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
commit 77655c298c
338 changed files with 63491 additions and 0 deletions

43
.gitignore vendored Normal file
View File

@ -0,0 +1,43 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
.venv/
venv/
ENV/
# Node
node_modules/
bun.lockb
# IDE
.idea/
.vscode/
*.swp
*.swo
# Secrets (never commit)
*.pem
*.key
.env
.env.*
credentials.json
# Data directories
dragonfly-data/
*.db-journal
# Build artifacts
dist/
build/
*.egg-info/
# Logs
*.log
logs/
# OS
.DS_Store
Thumbs.db

163
README.md Normal file
View File

@ -0,0 +1,163 @@
# Agent Governance System
> A comprehensive framework for governing AI agent execution with security, auditability, and coordination.
## Overview
The Agent Governance System provides infrastructure for running AI agents with:
- **Tiered permissions** (T0 observer, T1 executor, T2 admin)
- **Audit trails** via SQLite ledger
- **Secure credentials** via HashiCorp Vault
- **State coordination** via DragonflyDB
- **Pipeline orchestration** for multi-agent workflows
- **Context management** for long-running sessions
## Quick Start
```bash
# Check system status
checkpoint load # Load session state
status dashboard # View directory progress
memory stats # Check memory usage
# Create checkpoint after work
checkpoint now --notes "Description of completed work"
```
## Key Components
| Directory | Purpose | Status |
|-----------|---------|--------|
| `pipeline/` | Pipeline DSL and core definitions | ✅ Complete |
| `runtime/` | Agent lifecycle and governance | ✅ Complete |
| `checkpoint/` | Session state management | ✅ Complete |
| `memory/` | External memory layer | ✅ Complete |
| `teams/` | Hierarchical team framework | ✅ Complete |
| `analytics/` | Learning and pattern detection | ✅ Complete |
| `tests/` | Test suites including chaos tests | 🚧 In Progress |
## CLI Tools
### Context Management
```bash
# Checkpoints - session state snapshots
checkpoint now --notes "..." # Create checkpoint
checkpoint load # Load latest
checkpoint report # Combined status view
checkpoint timeline # History
# Status - per-directory tracking
status sweep # Check all directories
status update <dir> --phase <p> # Update status
status dashboard # Overview
# Memory - large content storage
memory log --stdin # Store from pipe
memory fetch <id> -s # Get summary
memory list # Browse entries
```
### Agent Operations
```bash
# Run chaos tests
python tests/multi-agent-chaos/orchestrator.py
# Validate pipelines
python pipeline/pipeline.py validate <file.yaml>
```
## Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ Agent Governance │
├──────────────┬──────────────┬──────────────┬───────────────┤
│ Agents │ Pipeline │ Runtime │ Context │
│ │ │
│ • T0 Observer│ • DSL Parser │ • Lifecycle │ • Checkpoints │
│ • T1 Executor│ • Stages │ • Governance │ • STATUS │
│ • T2 Admin │ • Templates │ • Revocation │ • Memory │
├──────────────┴──────────────┴──────────────┴───────────────┤
│ Infrastructure │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌────────────┐ │
│ │ Vault │ │ Dragonfly│ │ Ledger │ │ Evidence │ │
│ │ (secrets)│ │ (state) │ │ (audit) │ │ (artifacts)│ │
│ └──────────┘ └──────────┘ └──────────┘ └────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
## Documentation
| Document | Description |
|----------|-------------|
| [ARCHITECTURE.md](docs/ARCHITECTURE.md) | Full system design |
| [CONTEXT_MANAGEMENT.md](docs/CONTEXT_MANAGEMENT.md) | Checkpoints, STATUS, Memory |
| [MEMORY_LAYER.md](docs/MEMORY_LAYER.md) | External memory details |
| [STATUS_PROTOCOL.md](docs/STATUS_PROTOCOL.md) | Directory status protocol |
## Directory Structure
```
agent-governance/
├── agents/ # Agent implementations (T0, T1, T2)
├── analytics/ # Learning and pattern detection
├── bin/ # CLI tools (checkpoint, status, memory)
├── checkpoint/ # Session state management
├── docs/ # Documentation
├── evidence/ # Audit evidence packages
├── integrations/ # External integrations (GitHub, Slack)
├── ledger/ # SQLite audit ledger
├── memory/ # External memory layer
├── orchestrator/ # Multi-agent orchestration
├── pipeline/ # Pipeline DSL and templates
├── preflight/ # Pre-execution validation
├── runtime/ # Agent lifecycle governance
├── sandbox/ # Sandboxed execution (Terraform, Ansible)
├── schemas/ # JSON schemas
├── teams/ # Hierarchical team framework
├── tests/ # Test suites
└── wrappers/ # Tool wrappers
```
## Current Status
```
Progress: ███████░░░░░░░░░░░░░░░░░░░░░░░ 23%
✅ Complete: 14 directories
🚧 In Progress: 5 directories
```
Run `status dashboard` for current details.
## Recovery After Reset
```bash
# 1. Load checkpoint
checkpoint load
# 2. View combined status
checkpoint report
# 3. Check memory
memory list --limit 5
# 4. Resume work
status update ./target-dir --task "Resuming work"
```
## Dependencies
| Service | Purpose | Port |
|---------|---------|------|
| HashiCorp Vault | Secrets management | 8200 |
| DragonflyDB | State coordination | 6379 |
| SQLite | Audit ledger | File |
---
*Phase 8: Production Hardening - In Progress*
**Completed Phases:** 1-7 ✅ | Foundation, Vault, Pipeline, Promotion/Revocation, Agent Bootstrap, DSL/Templates/Testing, Teams/Learning

35
STATUS.md Normal file
View File

@ -0,0 +1,35 @@
# Status: Agent Governance
## 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:44 UTC
- **Phase**: IN_PROGRESS
- **Action**: Agent Governance System - Phase 7 complete, ongoing development
- **Details**: Phase updated to in_progress
### 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:44 UTC*

32
agents/README.md Normal file
View File

@ -0,0 +1,32 @@
# Agents
> Agent implementations and configurations
## Overview
This directory contains agent implementations and configurations.
## Key Files
| File | Description |
|------|-------------|
| *No files yet* | |
## Interfaces / APIs
*Document any APIs, CLI commands, or interfaces here.*
## Status
** Not Started**
See [STATUS.md](./STATUS.md) for detailed progress tracking.
## Architecture Reference
Part of the [Agent Governance System](/opt/agent-governance/docs/ARCHITECTURE.md).
Parent: [Project Root](/opt/agent-governance)
---
*Last updated: 2026-01-23 23:25:09 UTC*

35
agents/STATUS.md Normal file
View File

@ -0,0 +1,35 @@
# Status: Agents
## 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:39 UTC
- **Phase**: IN_PROGRESS
- **Action**: Agent implementations ongoing
- **Details**: Phase updated to in_progress
### 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:39 UTC*

34
agents/llm-planner-ts/.gitignore vendored Normal file
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

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`.

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.

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*

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=="],
}
}

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);
});

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);

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"
}
}

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
}
}

10
agents/llm-planner/.gitignore vendored Normal file
View File

@ -0,0 +1,10 @@
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv

View File

@ -0,0 +1 @@
3.11

View File

View File

@ -0,0 +1,30 @@
# Status: Llm Planner
## 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*

527
agents/llm-planner/agent.py Executable file
View File

@ -0,0 +1,527 @@
#!/usr/bin/env python3
"""
LLM Planner Agent - Tier 0 Observer
====================================
A compliant agent that uses OpenRouter LLMs for planning tasks.
Follows the Agent Foundation document constraints.
Capabilities:
- Read documentation and inventory from Vault
- Generate implementation plans using LLM
- Produce structured, auditable outputs
- Log all actions to governance ledger
"""
import json
import sqlite3
import subprocess
import sys
from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
from typing import Any, Optional
from openai import OpenAI
from pydantic import BaseModel, Field
# =============================================================================
# Agent Metadata (Section 4 of Foundation Doc)
# =============================================================================
AGENT_METADATA = {
"agent_id": "llm-planner-001",
"agent_role": "observer",
"owner": "system",
"version": "0.1.0",
"tier": 0,
"input_contract": "TaskRequest",
"output_contract": "AgentOutput",
"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",
"write_secrets",
"execute_shell",
"modify_files"
],
"confidence_reporting": True,
"confidence_threshold": 0.7,
}
# =============================================================================
# Structured Types (Section 6 of Foundation Doc)
# =============================================================================
class Decision(str, Enum):
EXECUTE = "EXECUTE"
SKIP = "SKIP"
ESCALATE = "ESCALATE"
INSUFFICIENT_INFORMATION = "INSUFFICIENT_INFORMATION"
ERROR = "ERROR"
class SideEffect(BaseModel):
type: str
target: str
reversible: bool = True
class ErrorInfo(BaseModel):
type: str
message: str
triggering_input: str
partial_progress: str
recommended_action: str
class AgentOutput(BaseModel):
"""Required output format per Foundation Doc Section 6"""
agent_id: str
version: str
timestamp: str
action: str
decision: Decision
confidence: float = Field(ge=0.0, le=1.0)
assumptions: list[str] = []
dependencies: list[str] = []
side_effects: list[SideEffect] = []
notes_for_humans: str = ""
error: Optional[ErrorInfo] = None
# Extended fields for LLM agent
llm_model: Optional[str] = None
llm_response: Optional[str] = None
plan: Optional[dict] = None
class TaskRequest(BaseModel):
"""Input schema for task requests"""
task_type: str # e.g., "generate_plan", "analyze", "summarize"
description: str
context: dict = {}
constraints: list[str] = []
# =============================================================================
# Vault Integration
# =============================================================================
class VaultClient:
def __init__(self, addr: str = "https://127.0.0.1:8200"):
self.addr = addr
self.token = self._load_token()
def _load_token(self) -> str:
with open("/opt/vault/init-keys.json") as f:
return json.load(f)["root_token"]
def read_secret(self, path: str) -> dict:
result = subprocess.run([
"curl", "-sk",
"-H", f"X-Vault-Token: {self.token}",
f"{self.addr}/v1/secret/data/{path}"
], capture_output=True, text=True)
data = json.loads(result.stdout)
if "data" in data and "data" in data["data"]:
return data["data"]["data"]
raise ValueError(f"Failed to read secret: {path}")
def get_openrouter_key(self) -> str:
return self.read_secret("api-keys/openrouter")["api_key"]
def get_inventory(self) -> dict:
return self.read_secret("inventory/proxmox")
def get_docs(self, doc_name: str) -> dict:
return self.read_secret(f"docs/{doc_name}")
# =============================================================================
# Ledger Integration
# =============================================================================
class Ledger:
def __init__(self, db_path: str = "/opt/agent-governance/ledger/governance.db"):
self.db_path = db_path
def log_action(self, output: AgentOutput, success: bool):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
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.value,
output.confidence,
1 if success else 0,
output.error.type if output.error else None,
output.error.message if output.error else None
))
# Update metrics
cursor.execute("""
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,
1 if success else 0, 1 if success else 0,
1 if success else 0, 1 if success else 0,
output.timestamp, output.timestamp
))
conn.commit()
conn.close()
# =============================================================================
# LLM Planner Agent
# =============================================================================
class LLMPlannerAgent:
"""
Tier 0 Observer Agent with LLM capabilities.
Invariant: If the agent cannot explain why it took an action,
that action is invalid. (Foundation Doc Section 1)
"""
def __init__(self, model: str = "anthropic/claude-3.5-haiku"):
self.metadata = AGENT_METADATA
self.model = model
self.vault = VaultClient()
self.ledger = Ledger()
# Initialize OpenRouter client
api_key = self.vault.get_openrouter_key()
self.llm = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=api_key
)
print(f"[INIT] Agent {self.metadata['agent_id']} v{self.metadata['version']} initialized")
print(f"[INIT] LLM Model: {self.model}")
print(f"[INIT] Tier: {self.metadata['tier']} ({self.metadata['agent_role']})")
def _now(self) -> str:
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
def _validate_action(self, action: str) -> bool:
"""Section 3.3: Bounded Authority - validate action is allowed"""
if action in self.metadata["forbidden_actions"]:
return False
if action in self.metadata["allowed_side_effects"]:
return True
# Default deny for unknown actions
return False
def _create_error_output(self, action: str, error: ErrorInfo) -> AgentOutput:
return AgentOutput(
agent_id=self.metadata["agent_id"],
version=self.metadata["version"],
timestamp=self._now(),
action=action,
decision=Decision.ERROR,
confidence=0.0,
error=error,
notes_for_humans="Action failed - see error details"
)
def _check_confidence(self, confidence: float) -> bool:
"""Section 7: Confidence below threshold = no irreversible actions"""
return confidence >= self.metadata["confidence_threshold"]
def generate_plan(self, request: TaskRequest) -> AgentOutput:
"""Generate an implementation plan using LLM"""
action = "generate_plan"
# Validate action is allowed
if not self._validate_action(action):
error = ErrorInfo(
type="FORBIDDEN_ACTION",
message=f"Action '{action}' is not permitted for this agent",
triggering_input=request.description,
partial_progress="None",
recommended_action="Escalate to higher tier agent"
)
output = self._create_error_output(action, error)
self.ledger.log_action(output, success=False)
return output
# Build context from Vault
try:
inventory = self.vault.get_inventory()
context_info = f"Infrastructure: {inventory.get('cluster', 'unknown')} with pools: {inventory.get('pools', 'unknown')}"
except Exception as e:
context_info = "Infrastructure context unavailable"
# Construct prompt
system_prompt = f"""You are a Tier 0 Observer agent in an infrastructure automation system.
Your role is to generate implementation plans - you CANNOT execute anything.
Agent Constraints:
- You can ONLY generate plans, not execute them
- Plans must be reversible and include rollback steps
- You must identify uncertainties and flag them
- All assumptions must be explicitly stated
Infrastructure Context:
{context_info}
Output your plan as JSON with this structure:
{{
"title": "Plan title",
"summary": "Brief summary",
"confidence": 0.0-1.0,
"assumptions": ["assumption 1", "assumption 2"],
"uncertainties": ["uncertainty 1"],
"steps": [
{{"step": 1, "action": "description", "reversible": true, "rollback": "how to undo"}}
],
"estimated_tier_required": 0-4,
"risks": ["risk 1"]
}}"""
user_prompt = f"""Task: {request.description}
Additional Context: {json.dumps(request.context) if request.context else 'None'}
Constraints: {', '.join(request.constraints) if request.constraints else 'None'}
Generate a detailed implementation plan."""
# Call LLM
try:
response = self.llm.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
],
max_tokens=2000,
temperature=0.3 # Lower temperature for more deterministic planning
)
llm_response = response.choices[0].message.content
# Try to parse the plan JSON
try:
# Extract JSON from response
json_start = llm_response.find('{')
json_end = llm_response.rfind('}') + 1
if json_start >= 0 and json_end > json_start:
plan = json.loads(llm_response[json_start:json_end])
confidence = plan.get("confidence", 0.5)
else:
plan = {"raw_response": llm_response}
confidence = 0.5
except json.JSONDecodeError:
plan = {"raw_response": llm_response}
confidence = 0.5
# Check confidence threshold
decision = Decision.EXECUTE if self._check_confidence(confidence) else Decision.ESCALATE
output = AgentOutput(
agent_id=self.metadata["agent_id"],
version=self.metadata["version"],
timestamp=self._now(),
action=action,
decision=decision,
confidence=confidence,
assumptions=plan.get("assumptions", []),
dependencies=[],
side_effects=[
SideEffect(type="llm_inference", target=self.model, reversible=True)
],
notes_for_humans=f"Plan generated. Estimated tier required: {plan.get('estimated_tier_required', 'unknown')}",
llm_model=self.model,
llm_response=llm_response[:500] + "..." if len(llm_response) > 500 else llm_response,
plan=plan
)
self.ledger.log_action(output, success=True)
return output
except Exception as e:
error = ErrorInfo(
type="LLM_ERROR",
message=str(e),
triggering_input=request.description,
partial_progress="Context loaded, LLM call failed",
recommended_action="Check API key and connectivity"
)
output = self._create_error_output(action, error)
self.ledger.log_action(output, success=False)
return output
def analyze(self, request: TaskRequest) -> AgentOutput:
"""Analyze a topic and provide insights"""
action = "read_docs" # Analysis is a form of reading
if not self._validate_action(action):
error = ErrorInfo(
type="FORBIDDEN_ACTION",
message=f"Action '{action}' is not permitted",
triggering_input=request.description,
partial_progress="None",
recommended_action="Check agent permissions"
)
output = self._create_error_output(action, error)
self.ledger.log_action(output, success=False)
return output
try:
response = self.llm.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": "You are a technical analyst. Provide clear, structured analysis. Be explicit about uncertainties."},
{"role": "user", "content": request.description}
],
max_tokens=1500,
temperature=0.4
)
llm_response = response.choices[0].message.content
output = AgentOutput(
agent_id=self.metadata["agent_id"],
version=self.metadata["version"],
timestamp=self._now(),
action=action,
decision=Decision.EXECUTE,
confidence=0.8,
assumptions=["Analysis based on provided context only"],
dependencies=[],
side_effects=[
SideEffect(type="llm_inference", target=self.model, reversible=True)
],
notes_for_humans="Analysis complete",
llm_model=self.model,
llm_response=llm_response
)
self.ledger.log_action(output, success=True)
return output
except Exception as e:
error = ErrorInfo(
type="LLM_ERROR",
message=str(e),
triggering_input=request.description,
partial_progress="None",
recommended_action="Retry or check connectivity"
)
output = self._create_error_output(action, error)
self.ledger.log_action(output, success=False)
return output
def run(self, request: TaskRequest) -> AgentOutput:
"""Main entry point - routes to appropriate handler"""
print(f"\n[TASK] Received: {request.task_type}")
print(f"[TASK] Description: {request.description[:100]}...")
handlers = {
"generate_plan": self.generate_plan,
"plan": self.generate_plan,
"analyze": self.analyze,
"analysis": self.analyze,
}
handler = handlers.get(request.task_type)
if handler:
return handler(request)
# Unknown task type
error = ErrorInfo(
type="UNKNOWN_TASK_TYPE",
message=f"Unknown task type: {request.task_type}",
triggering_input=json.dumps(request.model_dump()),
partial_progress="None",
recommended_action=f"Use one of: {list(handlers.keys())}"
)
output = self._create_error_output(request.task_type, error)
self.ledger.log_action(output, success=False)
return output
# =============================================================================
# CLI Interface
# =============================================================================
def main():
import argparse
parser = argparse.ArgumentParser(description="LLM Planner Agent")
parser.add_argument("task_type", choices=["plan", "analyze"], help="Type of task")
parser.add_argument("description", help="Task description")
parser.add_argument("--model", default="anthropic/claude-3.5-haiku", help="OpenRouter model")
parser.add_argument("--json", action="store_true", help="Output raw JSON")
args = parser.parse_args()
agent = LLMPlannerAgent(model=args.model)
request = TaskRequest(
task_type=args.task_type,
description=args.description
)
output = agent.run(request)
if args.json:
print(output.model_dump_json(indent=2))
else:
print("\n" + "="*60)
print(f"Decision: {output.decision.value}")
print(f"Confidence: {output.confidence}")
print(f"Model: {output.llm_model}")
print("="*60)
if output.plan:
print("\n📋 PLAN:")
print(json.dumps(output.plan, indent=2))
elif output.llm_response:
print("\n📝 RESPONSE:")
print(output.llm_response)
if output.error:
print("\n❌ ERROR:")
print(f" Type: {output.error.type}")
print(f" Message: {output.error.message}")
print(f" Recommendation: {output.error.recommended_action}")
if output.assumptions:
print("\n⚠️ ASSUMPTIONS:")
for a in output.assumptions:
print(f" - {a}")
print("\n" + "="*60)
if __name__ == "__main__":
main()

736
agents/llm-planner/governance.py Executable file
View File

@ -0,0 +1,736 @@
#!/usr/bin/env python3
"""
Agent Runtime Governance via DragonflyDB
=========================================
Implements real-time agent control: instruction packets, state tracking,
error budgets, revocation, and handoffs.
"""
import json
import hashlib
import time
from dataclasses import dataclass, field, asdict
from datetime import datetime, timezone
from enum import Enum
from typing import Any, Optional
import redis
import subprocess
# =============================================================================
# Configuration
# =============================================================================
def get_dragonfly_client() -> redis.Redis:
"""Get DragonflyDB client with credentials from Vault"""
# Get credentials from Vault
with open("/opt/vault/init-keys.json") as f:
token = json.load(f)["root_token"]
result = subprocess.run([
"curl", "-sk",
"-H", f"X-Vault-Token: {token}",
"https://127.0.0.1:8200/v1/secret/data/services/dragonfly"
], capture_output=True, text=True)
creds = json.loads(result.stdout)["data"]["data"]
return redis.Redis(
host=creds["host"],
port=int(creds["port"]),
password=creds["password"],
decode_responses=True
)
# =============================================================================
# Enums and Types
# =============================================================================
class AgentPhase(str, Enum):
BOOTSTRAP = "BOOTSTRAP"
PREFLIGHT = "PREFLIGHT"
PLAN = "PLAN"
EXECUTE = "EXECUTE"
VERIFY = "VERIFY"
PACKAGE = "PACKAGE"
REPORT = "REPORT"
EXIT = "EXIT"
REVOKED = "REVOKED"
class AgentStatus(str, Enum):
PENDING = "PENDING"
RUNNING = "RUNNING"
PAUSED = "PAUSED"
COMPLETED = "COMPLETED"
REVOKED = "REVOKED"
FAILED = "FAILED"
class RevocationType(str, Enum):
ERROR_BUDGET_EXCEEDED = "ERROR_BUDGET_EXCEEDED"
PROCEDURE_VIOLATION = "PROCEDURE_VIOLATION"
FORBIDDEN_ACTION = "FORBIDDEN_ACTION"
HEARTBEAT_TIMEOUT = "HEARTBEAT_TIMEOUT"
LOCK_EXPIRED = "LOCK_EXPIRED"
MANUAL = "MANUAL"
# =============================================================================
# Data Classes
# =============================================================================
@dataclass
class ErrorBudget:
max_total_errors: int = 12
max_same_error_repeats: int = 3
max_procedure_violations: int = 1
@dataclass
class InstructionPacket:
agent_id: str
task_id: str
created_for: str
objective: str
deliverables: list[str]
constraints: dict
success_criteria: list[str]
error_budget: ErrorBudget
escalation_rules: list[str]
repo: str = ""
pr_context: dict = field(default_factory=dict)
revocation_context: dict = field(default_factory=dict)
created_at: str = ""
def __post_init__(self):
if not self.created_at:
self.created_at = datetime.now(timezone.utc).isoformat()
if isinstance(self.error_budget, dict):
self.error_budget = ErrorBudget(**self.error_budget)
def to_dict(self) -> dict:
d = asdict(self)
d["error_budget"] = asdict(self.error_budget)
return d
@dataclass
class AgentState:
agent_id: str
status: AgentStatus
phase: AgentPhase
step: str = ""
started_at: str = ""
last_progress_at: str = ""
current_pr: Optional[int] = None
notes: str = ""
def to_dict(self) -> dict:
return {
"agent_id": self.agent_id,
"status": self.status.value,
"phase": self.phase.value,
"step": self.step,
"started_at": self.started_at,
"last_progress_at": self.last_progress_at,
"current_pr": self.current_pr,
"notes": self.notes
}
@dataclass
class HandoffObject:
task_id: str
previous_agent_id: str
revoked: bool
revocation_reason: dict
last_known_state: dict
what_was_tried: list[str]
blocking_issue: str
required_next_actions: list[str]
constraints_reminder: list[str]
artifacts: list[str]
created_at: str = ""
def __post_init__(self):
if not self.created_at:
self.created_at = datetime.now(timezone.utc).isoformat()
def to_dict(self) -> dict:
return asdict(self)
# =============================================================================
# Governance Manager
# =============================================================================
class GovernanceManager:
"""
Core runtime governance via DragonflyDB.
Manages instruction packets, state, errors, locks, and handoffs.
"""
def __init__(self):
self.db = get_dragonfly_client()
self.lock_ttl = 300 # 5 minutes default lock TTL
self.heartbeat_ttl = 60 # 1 minute heartbeat timeout
def _now(self) -> str:
return datetime.now(timezone.utc).isoformat()
def _error_signature(self, error_type: str, message: str) -> str:
"""Generate a normalized error signature"""
normalized = f"{error_type}:{message[:100]}".lower()
return hashlib.md5(normalized.encode()).hexdigest()[:12]
# -------------------------------------------------------------------------
# Instruction Packets
# -------------------------------------------------------------------------
def create_instruction_packet(self, packet: InstructionPacket) -> bool:
"""Store an instruction packet for an agent"""
key = f"agent:{packet.agent_id}:packet"
return self.db.set(key, json.dumps(packet.to_dict()))
def get_instruction_packet(self, agent_id: str) -> Optional[InstructionPacket]:
"""Retrieve an agent's instruction packet"""
key = f"agent:{agent_id}:packet"
data = self.db.get(key)
if data:
return InstructionPacket(**json.loads(data))
return None
# -------------------------------------------------------------------------
# Agent State
# -------------------------------------------------------------------------
def set_agent_state(self, state: AgentState) -> bool:
"""Update agent's runtime state"""
state.last_progress_at = self._now()
key = f"agent:{state.agent_id}:state"
return self.db.set(key, json.dumps(state.to_dict()))
def get_agent_state(self, agent_id: str) -> Optional[AgentState]:
"""Get agent's current state"""
key = f"agent:{agent_id}:state"
data = self.db.get(key)
if data:
d = json.loads(data)
return AgentState(
agent_id=d["agent_id"],
status=AgentStatus(d["status"]),
phase=AgentPhase(d["phase"]),
step=d.get("step", ""),
started_at=d.get("started_at", ""),
last_progress_at=d.get("last_progress_at", ""),
current_pr=d.get("current_pr"),
notes=d.get("notes", "")
)
return None
def transition_phase(self, agent_id: str, phase: AgentPhase, step: str = "", notes: str = "") -> bool:
"""Transition agent to a new phase"""
state = self.get_agent_state(agent_id)
if not state:
return False
state.phase = phase
state.step = step
state.notes = notes
return self.set_agent_state(state)
# -------------------------------------------------------------------------
# Locking
# -------------------------------------------------------------------------
def acquire_lock(self, agent_id: str, ttl: int = None) -> bool:
"""Acquire execution lock for an agent"""
key = f"agent:{agent_id}:lock"
ttl = ttl or self.lock_ttl
# NX = only set if not exists
return self.db.set(key, self._now(), nx=True, ex=ttl)
def refresh_lock(self, agent_id: str, ttl: int = None) -> bool:
"""Refresh an existing lock"""
key = f"agent:{agent_id}:lock"
ttl = ttl or self.lock_ttl
if self.db.exists(key):
return self.db.expire(key, ttl)
return False
def release_lock(self, agent_id: str) -> bool:
"""Release execution lock"""
key = f"agent:{agent_id}:lock"
return self.db.delete(key) > 0
def has_lock(self, agent_id: str) -> bool:
"""Check if agent has a valid lock"""
key = f"agent:{agent_id}:lock"
return self.db.exists(key)
# -------------------------------------------------------------------------
# Heartbeat
# -------------------------------------------------------------------------
def heartbeat(self, agent_id: str) -> bool:
"""Update agent heartbeat"""
key = f"agent:{agent_id}:heartbeat"
return self.db.set(key, self._now(), ex=self.heartbeat_ttl)
def is_alive(self, agent_id: str) -> bool:
"""Check if agent heartbeat is fresh"""
key = f"agent:{agent_id}:heartbeat"
return self.db.exists(key)
# -------------------------------------------------------------------------
# Error Tracking
# -------------------------------------------------------------------------
def record_error(self, agent_id: str, error_type: str, message: str) -> dict:
"""Record an error and return current counts"""
key = f"agent:{agent_id}:errors"
sig = self._error_signature(error_type, message)
pipe = self.db.pipeline()
pipe.hincrby(key, "total_errors", 1)
pipe.hincrby(key, f"same_error:{sig}", 1)
pipe.hset(key, "last_error_signature", sig)
pipe.hset(key, "last_error_at", self._now())
pipe.hset(key, "last_error_type", error_type)
pipe.hset(key, "last_error_message", message[:500])
pipe.execute()
return self.get_error_counts(agent_id)
def record_procedure_violation(self, agent_id: str, violation: str) -> int:
"""Record a procedure violation"""
key = f"agent:{agent_id}:errors"
self.db.hset(key, "last_violation", violation)
self.db.hset(key, "last_violation_at", self._now())
return self.db.hincrby(key, "procedure_violations", 1)
def get_error_counts(self, agent_id: str) -> dict:
"""Get all error counts for an agent"""
key = f"agent:{agent_id}:errors"
data = self.db.hgetall(key)
return {
"total_errors": int(data.get("total_errors", 0)),
"procedure_violations": int(data.get("procedure_violations", 0)),
"last_error_signature": data.get("last_error_signature", ""),
"last_error_at": data.get("last_error_at", ""),
"last_error_type": data.get("last_error_type", ""),
"same_error_counts": {
k.replace("same_error:", ""): int(v)
for k, v in data.items()
if k.startswith("same_error:")
}
}
def check_error_budget(self, agent_id: str) -> tuple[bool, Optional[str]]:
"""Check if error budget is exceeded. Returns (ok, reason)"""
packet = self.get_instruction_packet(agent_id)
if not packet:
return False, "NO_INSTRUCTION_PACKET"
counts = self.get_error_counts(agent_id)
budget = packet.error_budget
# Check procedure violations
if counts["procedure_violations"] >= budget.max_procedure_violations:
return False, f"PROCEDURE_VIOLATIONS ({counts['procedure_violations']} >= {budget.max_procedure_violations})"
# Check total errors
if counts["total_errors"] >= budget.max_total_errors:
return False, f"TOTAL_ERRORS ({counts['total_errors']} >= {budget.max_total_errors})"
# Check same error repeats
for sig, count in counts["same_error_counts"].items():
if count >= budget.max_same_error_repeats:
return False, f"SAME_ERROR_REPEATED ({sig}: {count} >= {budget.max_same_error_repeats})"
return True, None
# -------------------------------------------------------------------------
# Task Management
# -------------------------------------------------------------------------
def assign_agent_to_task(self, task_id: str, agent_id: str) -> bool:
"""Assign an agent to a task"""
# Set active agent
self.db.set(f"task:{task_id}:active_agent", agent_id)
# Add to history
self.db.rpush(f"task:{task_id}:history", json.dumps({
"agent_id": agent_id,
"assigned_at": self._now(),
"event": "ASSIGNED"
}))
return True
def get_active_agent(self, task_id: str) -> Optional[str]:
"""Get the currently active agent for a task"""
return self.db.get(f"task:{task_id}:active_agent")
def get_task_history(self, task_id: str) -> list[dict]:
"""Get task agent history"""
data = self.db.lrange(f"task:{task_id}:history", 0, -1)
return [json.loads(d) for d in data]
# -------------------------------------------------------------------------
# Revocation
# -------------------------------------------------------------------------
def revoke_agent(self, agent_id: str, reason_type: RevocationType, details: str) -> bool:
"""Revoke an agent's access"""
# Update state
state = self.get_agent_state(agent_id)
if state:
state.status = AgentStatus.REVOKED
state.phase = AgentPhase.REVOKED
state.notes = f"Revoked: {reason_type.value} - {details}"
self.set_agent_state(state)
# Release lock
self.release_lock(agent_id)
# Write to revocation ledger
revocation_event = {
"agent_id": agent_id,
"reason_type": reason_type.value,
"details": details,
"revoked_at": self._now()
}
self.db.rpush("revocations:ledger", json.dumps(revocation_event))
# Add to task history if we know the task
packet = self.get_instruction_packet(agent_id)
if packet:
self.db.rpush(f"task:{packet.task_id}:history", json.dumps({
"agent_id": agent_id,
"event": "REVOKED",
"reason": reason_type.value,
"revoked_at": self._now()
}))
return True
def get_recent_revocations(self, count: int = 50) -> list[dict]:
"""Get recent revocation events"""
data = self.db.lrange("revocations:ledger", -count, -1)
return [json.loads(d) for d in data]
# -------------------------------------------------------------------------
# Handoff
# -------------------------------------------------------------------------
def create_handoff(self, handoff: HandoffObject) -> bool:
"""Create a handoff object for task continuity"""
key = f"handoff:{handoff.task_id}:latest"
return self.db.set(key, json.dumps(handoff.to_dict()))
def get_handoff(self, task_id: str) -> Optional[HandoffObject]:
"""Get the latest handoff for a task"""
key = f"handoff:{task_id}:latest"
data = self.db.get(key)
if data:
return HandoffObject(**json.loads(data))
return None
# -------------------------------------------------------------------------
# Artifacts
# -------------------------------------------------------------------------
def register_artifact(self, task_id: str, artifact_type: str, reference: str) -> bool:
"""Register an artifact for a task"""
key = f"task:{task_id}:artifacts"
artifact = {
"type": artifact_type,
"reference": reference,
"created_at": self._now()
}
self.db.rpush(key, json.dumps(artifact))
return True
def get_artifacts(self, task_id: str) -> list[dict]:
"""Get all artifacts for a task"""
key = f"task:{task_id}:artifacts"
data = self.db.lrange(key, 0, -1)
return [json.loads(d) for d in data]
def has_required_artifact(self, task_id: str, artifact_type: str) -> bool:
"""Check if a required artifact exists"""
artifacts = self.get_artifacts(task_id)
return any(a["type"] == artifact_type for a in artifacts)
# =============================================================================
# Worker Agent Runtime
# =============================================================================
class WorkerRuntime:
"""
Runtime for worker agents.
Handles bootstrap, state transitions, error reporting, and compliance.
"""
def __init__(self, agent_id: str):
self.agent_id = agent_id
self.gov = GovernanceManager()
self.packet: Optional[InstructionPacket] = None
self.state: Optional[AgentState] = None
def bootstrap(self) -> tuple[bool, str]:
"""
Bootstrap the agent runtime.
Returns (success, message)
"""
# Step 1: Read revocation ledger
revocations = self.gov.get_recent_revocations(50)
if revocations:
print(f"[BOOTSTRAP] Read {len(revocations)} recent revocations")
# Check if this agent was previously revoked
for rev in revocations:
if rev["agent_id"] == self.agent_id:
return False, f"AGENT_PREVIOUSLY_REVOKED: {rev['reason_type']}"
# Step 2: Read instruction packet
self.packet = self.gov.get_instruction_packet(self.agent_id)
if not self.packet:
return False, "NO_INSTRUCTION_PACKET"
print(f"[BOOTSTRAP] Loaded instruction packet for task: {self.packet.task_id}")
# Step 3: Check for existing handoff
handoff = self.gov.get_handoff(self.packet.task_id)
if handoff and handoff.previous_agent_id != self.agent_id:
print(f"[BOOTSTRAP] Found handoff from previous agent: {handoff.previous_agent_id}")
print(f"[BOOTSTRAP] Revocation reason: {handoff.revocation_reason}")
print(f"[BOOTSTRAP] Required next actions: {handoff.required_next_actions}")
# Step 4: Acquire lock
if not self.gov.acquire_lock(self.agent_id):
return False, "CANNOT_ACQUIRE_LOCK"
# Step 5: Initialize state
self.state = AgentState(
agent_id=self.agent_id,
status=AgentStatus.RUNNING,
phase=AgentPhase.BOOTSTRAP,
step="initialized",
started_at=datetime.now(timezone.utc).isoformat()
)
self.gov.set_agent_state(self.state)
# Step 6: Start heartbeat
self.gov.heartbeat(self.agent_id)
# Step 7: Assign to task
self.gov.assign_agent_to_task(self.packet.task_id, self.agent_id)
return True, "BOOTSTRAP_COMPLETE"
def transition(self, phase: AgentPhase, step: str = "", notes: str = "") -> bool:
"""Transition to a new phase"""
if not self.state:
return False
# Refresh heartbeat and lock
self.gov.heartbeat(self.agent_id)
self.gov.refresh_lock(self.agent_id)
# Check error budget before transition
ok, reason = self.gov.check_error_budget(self.agent_id)
if not ok:
self.gov.revoke_agent(self.agent_id, RevocationType.ERROR_BUDGET_EXCEEDED, reason)
return False
# Update state
self.state.phase = phase
self.state.step = step
self.state.notes = notes
self.gov.set_agent_state(self.state)
print(f"[PHASE] {phase.value} - {step}")
return True
def report_error(self, error_type: str, message: str) -> bool:
"""Report an error and check if we should continue"""
counts = self.gov.record_error(self.agent_id, error_type, message)
print(f"[ERROR] {error_type}: {message}")
print(f"[ERROR] Counts: total={counts['total_errors']}, violations={counts['procedure_violations']}")
ok, reason = self.gov.check_error_budget(self.agent_id)
if not ok:
print(f"[REVOKE] Error budget exceeded: {reason}")
self.gov.revoke_agent(self.agent_id, RevocationType.ERROR_BUDGET_EXCEEDED, reason)
return False
return True
def report_violation(self, violation: str) -> bool:
"""Report a procedure violation"""
count = self.gov.record_procedure_violation(self.agent_id, violation)
print(f"[VIOLATION] {violation} (count: {count})")
ok, reason = self.gov.check_error_budget(self.agent_id)
if not ok:
print(f"[REVOKE] Procedure violation limit: {reason}")
self.gov.revoke_agent(self.agent_id, RevocationType.PROCEDURE_VIOLATION, reason)
return False
return True
def register_artifact(self, artifact_type: str, reference: str):
"""Register a work artifact"""
if self.packet:
self.gov.register_artifact(self.packet.task_id, artifact_type, reference)
print(f"[ARTIFACT] Registered: {artifact_type} -> {reference}")
def complete(self, notes: str = "") -> bool:
"""Mark work as complete"""
if self.state:
self.state.status = AgentStatus.COMPLETED
self.state.phase = AgentPhase.EXIT
self.state.notes = notes
self.gov.set_agent_state(self.state)
self.gov.release_lock(self.agent_id)
print(f"[COMPLETE] {notes}")
return True
def create_handoff(self, blocking_issue: str, what_was_tried: list[str],
required_next_actions: list[str]) -> bool:
"""Create a handoff for the next agent"""
if not self.packet or not self.state:
return False
handoff = HandoffObject(
task_id=self.packet.task_id,
previous_agent_id=self.agent_id,
revoked=self.state.status == AgentStatus.REVOKED,
revocation_reason={
"type": self.state.status.value,
"details": self.state.notes
},
last_known_state={
"phase": self.state.phase.value,
"step": self.state.step
},
what_was_tried=what_was_tried,
blocking_issue=blocking_issue,
required_next_actions=required_next_actions,
constraints_reminder=self.packet.constraints.get("forbidden", []),
artifacts=[a["reference"] for a in self.gov.get_artifacts(self.packet.task_id)]
)
return self.gov.create_handoff(handoff)
# =============================================================================
# CLI
# =============================================================================
if __name__ == "__main__":
import sys
gov = GovernanceManager()
if len(sys.argv) < 2:
print("Usage: governance.py <command> [args]")
print("Commands:")
print(" create-packet <agent_id> <task_id> <objective>")
print(" get-state <agent_id>")
print(" get-errors <agent_id>")
print(" revocations")
print(" test")
sys.exit(1)
cmd = sys.argv[1]
if cmd == "create-packet":
agent_id, task_id, objective = sys.argv[2], sys.argv[3], sys.argv[4]
packet = InstructionPacket(
agent_id=agent_id,
task_id=task_id,
created_for="CLI test",
objective=objective,
deliverables=["plan artifacts", "run logs"],
constraints={
"scope": ["sandbox only"],
"forbidden": ["no prod access", "no unrecorded root"],
"required_steps": ["plan before apply"]
},
success_criteria=["CI passes", "artifacts uploaded"],
error_budget=ErrorBudget(),
escalation_rules=["If blocked > 20m -> escalate"]
)
gov.create_instruction_packet(packet)
print(f"Created packet for {agent_id}")
elif cmd == "get-state":
agent_id = sys.argv[2]
state = gov.get_agent_state(agent_id)
if state:
print(json.dumps(state.to_dict(), indent=2))
else:
print("No state found")
elif cmd == "get-errors":
agent_id = sys.argv[2]
errors = gov.get_error_counts(agent_id)
print(json.dumps(errors, indent=2))
elif cmd == "revocations":
revs = gov.get_recent_revocations()
for r in revs:
print(json.dumps(r))
elif cmd == "test":
print("=== Testing Governance System ===\n")
# Create test packet
packet = InstructionPacket(
agent_id="test-agent-001",
task_id="test-task-001",
created_for="Governance test",
objective="Test the governance system",
deliverables=["test output"],
constraints={
"scope": ["sandbox"],
"forbidden": ["prod access"],
"required_steps": ["plan first"]
},
success_criteria=["test passes"],
error_budget=ErrorBudget(max_total_errors=5, max_same_error_repeats=2),
escalation_rules=[]
)
gov.create_instruction_packet(packet)
print("[OK] Created instruction packet")
# Test worker runtime
worker = WorkerRuntime("test-agent-001")
ok, msg = worker.bootstrap()
print(f"[OK] Bootstrap: {msg}")
# Simulate work phases
worker.transition(AgentPhase.PREFLIGHT, "scope_check")
worker.transition(AgentPhase.PLAN, "generating_plan")
worker.register_artifact("plan", "plan_output_001")
worker.transition(AgentPhase.EXECUTE, "applying")
# Simulate some errors
worker.report_error("VALIDATION_ERROR", "Variable not defined")
worker.report_error("VALIDATION_ERROR", "Variable not defined") # Same error
# Check error budget
ok, reason = gov.check_error_budget("test-agent-001")
print(f"[OK] Error budget check: ok={ok}, reason={reason}")
# Complete
worker.complete("Test completed successfully")
print("\n=== Test Complete ===")
else:
print(f"Unknown command: {cmd}")

View File

@ -0,0 +1,397 @@
#!/usr/bin/env python3
"""
Governed LLM Agent
==================
An LLM-powered agent that operates under the governance runtime.
Combines planning capabilities with full governance compliance.
"""
import json
import sys
from datetime import datetime, timezone
from typing import Optional
from openai import OpenAI
from pydantic import BaseModel
from governance import (
GovernanceManager,
WorkerRuntime,
InstructionPacket,
ErrorBudget,
AgentPhase,
AgentStatus,
)
# =============================================================================
# Configuration
# =============================================================================
def get_openrouter_client() -> OpenAI:
"""Get OpenRouter client with API key from Vault"""
import subprocess
with open("/opt/vault/init-keys.json") as f:
token = json.load(f)["root_token"]
result = subprocess.run([
"curl", "-sk",
"-H", f"X-Vault-Token: {token}",
"https://127.0.0.1:8200/v1/secret/data/api-keys/openrouter"
], capture_output=True, text=True)
api_key = json.loads(result.stdout)["data"]["data"]["api_key"]
return OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=api_key
)
# =============================================================================
# Governed LLM Agent
# =============================================================================
class GovernedLLMAgent:
"""
An LLM agent that operates under governance control.
Lifecycle:
1. Receive instruction packet
2. Bootstrap with governance runtime
3. Execute phases: PREFLIGHT -> PLAN -> EXECUTE -> VERIFY -> PACKAGE -> REPORT
4. Handle errors within budget
5. Create handoff if revoked
"""
def __init__(self, agent_id: str, model: str = "anthropic/claude-sonnet-4"):
self.agent_id = agent_id
self.model = model
self.gov = GovernanceManager()
self.runtime: Optional[WorkerRuntime] = None
self.llm: Optional[OpenAI] = None
def _now(self) -> str:
return datetime.now(timezone.utc).isoformat()
def create_task(self, task_id: str, objective: str, constraints: dict = None) -> bool:
"""Create an instruction packet for this agent"""
packet = InstructionPacket(
agent_id=self.agent_id,
task_id=task_id,
created_for="Governed LLM Task",
objective=objective,
deliverables=["implementation plan", "execution logs", "artifacts"],
constraints=constraints or {
"scope": ["sandbox only"],
"forbidden": ["no prod access", "no unrecorded changes"],
"required_steps": ["plan before execute", "verify after execute"]
},
success_criteria=["plan generated", "artifacts registered"],
error_budget=ErrorBudget(
max_total_errors=10,
max_same_error_repeats=3,
max_procedure_violations=1
),
escalation_rules=[
"If confidence < 0.7 -> escalate",
"If blocked > 10m -> escalate"
]
)
return self.gov.create_instruction_packet(packet)
def start(self) -> tuple[bool, str]:
"""Bootstrap the governed agent"""
print(f"\n{'='*60}")
print(f"GOVERNED LLM AGENT: {self.agent_id}")
print(f"Model: {self.model}")
print(f"{'='*60}\n")
# Initialize runtime
self.runtime = WorkerRuntime(self.agent_id)
# Bootstrap (reads revocations, loads packet, acquires lock)
ok, msg = self.runtime.bootstrap()
if not ok:
print(f"[FATAL] Bootstrap failed: {msg}")
return False, msg
# Initialize LLM client
self.llm = get_openrouter_client()
print(f"[READY] Agent bootstrapped successfully")
print(f"[TASK] {self.runtime.packet.objective}")
print(f"[CONSTRAINTS] {self.runtime.packet.constraints}")
return True, "READY"
def run_preflight(self) -> bool:
"""PREFLIGHT phase: scope and dependency checks"""
if not self.runtime:
return False
if not self.runtime.transition(AgentPhase.PREFLIGHT, "scope_check"):
return False
packet = self.runtime.packet
# Check scope constraints
scope = packet.constraints.get("scope", [])
print(f"[PREFLIGHT] Scope constraints: {scope}")
# Check forbidden actions
forbidden = packet.constraints.get("forbidden", [])
print(f"[PREFLIGHT] Forbidden actions: {forbidden}")
# Check required steps
required = packet.constraints.get("required_steps", [])
print(f"[PREFLIGHT] Required steps: {required}")
self.runtime.transition(AgentPhase.PREFLIGHT, "preflight_complete",
"All preflight checks passed")
return True
def run_plan(self) -> Optional[dict]:
"""PLAN phase: generate implementation plan using LLM"""
if not self.runtime or not self.llm:
return None
if not self.runtime.transition(AgentPhase.PLAN, "generating_plan"):
return None
packet = self.runtime.packet
# Build prompt
system_prompt = f"""You are a governed infrastructure agent operating under strict constraints.
Your task: {packet.objective}
Constraints you MUST follow:
- Scope: {packet.constraints.get('scope', [])}
- Forbidden: {packet.constraints.get('forbidden', [])}
- Required steps: {packet.constraints.get('required_steps', [])}
You are in the PLAN phase. Generate a detailed plan but DO NOT execute anything.
Output your plan as JSON:
{{
"title": "Plan title",
"confidence": 0.0-1.0,
"steps": [
{{"step": 1, "action": "description", "phase": "PLAN|EXECUTE|VERIFY", "reversible": true}}
],
"assumptions": [],
"risks": [],
"estimated_duration": "X minutes"
}}"""
try:
response = self.llm.chat.completions.create(
model=self.model,
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": f"Create an implementation plan for: {packet.objective}"}
],
max_tokens=2000,
temperature=0.3
)
llm_response = response.choices[0].message.content
# Parse plan
try:
json_match = llm_response[llm_response.find("{"):llm_response.rfind("}")+1]
plan = json.loads(json_match)
except:
plan = {"raw_response": llm_response, "confidence": 0.5}
# Register plan artifact
self.runtime.register_artifact("plan", f"plan_{self.agent_id}_{self._now()}")
# Check confidence
confidence = plan.get("confidence", 0.5)
if confidence < 0.7:
print(f"[PLAN] Low confidence ({confidence}), would escalate in production")
self.runtime.transition(AgentPhase.PLAN, "plan_complete",
f"Plan generated with confidence {confidence}")
return plan
except Exception as e:
self.runtime.report_error("LLM_ERROR", str(e))
return None
def run_execute(self, plan: dict) -> bool:
"""EXECUTE phase: simulate execution (in real system, would apply changes)"""
if not self.runtime:
return False
# Verify we have a plan artifact (compliance requirement)
if not self.gov.has_required_artifact(self.runtime.packet.task_id, "plan"):
self.runtime.report_violation("EXECUTE_WITHOUT_PLAN")
return False
if not self.runtime.transition(AgentPhase.EXECUTE, "executing"):
return False
steps = plan.get("steps", [])
print(f"[EXECUTE] Simulating execution of {len(steps)} steps...")
for step in steps:
step_num = step.get("step", "?")
action = step.get("action", "unknown")
print(f" Step {step_num}: {action[:60]}...")
# In real implementation, would execute the action here
# For now, just register it as done
self.runtime.register_artifact(
f"step_{step_num}",
f"executed_{step_num}_{self._now()}"
)
self.runtime.transition(AgentPhase.EXECUTE, "execute_complete")
return True
def run_verify(self) -> bool:
"""VERIFY phase: post-execution checks"""
if not self.runtime:
return False
if not self.runtime.transition(AgentPhase.VERIFY, "verifying"):
return False
# Check all artifacts were created
artifacts = self.gov.get_artifacts(self.runtime.packet.task_id)
print(f"[VERIFY] Registered artifacts: {len(artifacts)}")
# In real system, would run actual verification
self.runtime.transition(AgentPhase.VERIFY, "verify_complete",
f"Verified {len(artifacts)} artifacts")
return True
def run_package(self) -> dict:
"""PACKAGE phase: collect all outputs"""
if not self.runtime:
return {}
if not self.runtime.transition(AgentPhase.PACKAGE, "packaging"):
return {}
artifacts = self.gov.get_artifacts(self.runtime.packet.task_id)
errors = self.gov.get_error_counts(self.agent_id)
package = {
"agent_id": self.agent_id,
"task_id": self.runtime.packet.task_id,
"objective": self.runtime.packet.objective,
"artifacts": artifacts,
"error_counts": errors,
"completed_at": self._now()
}
self.runtime.register_artifact("package", f"package_{self._now()}")
self.runtime.transition(AgentPhase.PACKAGE, "package_complete")
return package
def run_report(self, package: dict) -> dict:
"""REPORT phase: generate final report"""
if not self.runtime:
return {}
if not self.runtime.transition(AgentPhase.REPORT, "reporting"):
return {}
report = {
"agent_id": self.agent_id,
"task_id": package.get("task_id"),
"status": "COMPLETED",
"summary": f"Completed objective: {package.get('objective')}",
"artifacts_count": len(package.get("artifacts", [])),
"errors_encountered": package.get("error_counts", {}).get("total_errors", 0),
"timestamp": self._now()
}
self.runtime.transition(AgentPhase.REPORT, "report_complete")
return report
def finish(self, report: dict) -> bool:
"""Complete the agent's work"""
if not self.runtime:
return False
return self.runtime.complete(f"Task completed: {report.get('summary', 'done')}")
def run_full_lifecycle(self) -> dict:
"""Run the complete agent lifecycle"""
# Start
ok, msg = self.start()
if not ok:
return {"status": "FAILED", "reason": msg}
# Preflight
if not self.run_preflight():
return {"status": "FAILED", "reason": "PREFLIGHT_FAILED"}
# Plan
plan = self.run_plan()
if not plan:
return {"status": "FAILED", "reason": "PLAN_FAILED"}
print(f"\n[PLAN GENERATED]")
print(json.dumps(plan, indent=2))
# Execute
if not self.run_execute(plan):
return {"status": "FAILED", "reason": "EXECUTE_FAILED"}
# Verify
if not self.run_verify():
return {"status": "FAILED", "reason": "VERIFY_FAILED"}
# Package
package = self.run_package()
# Report
report = self.run_report(package)
# Finish
self.finish(report)
print(f"\n{'='*60}")
print("FINAL REPORT")
print(f"{'='*60}")
print(json.dumps(report, indent=2))
return report
# =============================================================================
# CLI
# =============================================================================
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: governed_agent.py <agent_id> <task_id> <objective>")
print(" governed_agent.py <agent_id> <task_id> <objective> --model <model>")
sys.exit(1)
agent_id = sys.argv[1]
task_id = sys.argv[2]
objective = sys.argv[3]
model = "anthropic/claude-sonnet-4"
if "--model" in sys.argv:
model = sys.argv[sys.argv.index("--model") + 1]
# Create agent
agent = GovernedLLMAgent(agent_id, model=model)
# Create task
agent.create_task(task_id, objective)
# Run full lifecycle
result = agent.run_full_lifecycle()
sys.exit(0 if result.get("status") == "COMPLETED" else 1)

View File

@ -0,0 +1,6 @@
def main():
print("Hello from llm-planner!")
if __name__ == "__main__":
main()

317
agents/llm-planner/monitors.py Executable file
View File

@ -0,0 +1,317 @@
#!/usr/bin/env python3
"""
Governance Monitor Agents
=========================
These agents watch and enforce, they don't do work.
- Execution Watcher: Heartbeats, stuck detection, lock expiry
- Compliance Watcher: Artifact checks, forbidden action detection
- Vault Lease Watcher: Token validity, revocation enforcement
"""
import json
import time
from datetime import datetime, timezone, timedelta
from typing import Optional
import threading
from governance import (
GovernanceManager,
AgentPhase,
AgentStatus,
RevocationType
)
class ExecutionWatcher:
"""
Monitors agent execution health.
- Checks heartbeats
- Detects stuck agents
- Handles lock expiry
"""
def __init__(self, heartbeat_timeout: int = 60, stuck_threshold: int = 300):
self.gov = GovernanceManager()
self.heartbeat_timeout = heartbeat_timeout # seconds
self.stuck_threshold = stuck_threshold # seconds without progress
def check_agent(self, agent_id: str) -> dict:
"""Check an agent's execution health"""
result = {
"agent_id": agent_id,
"healthy": True,
"issues": []
}
state = self.gov.get_agent_state(agent_id)
if not state:
result["healthy"] = False
result["issues"].append("NO_STATE")
return result
# Skip completed/revoked agents
if state.status in [AgentStatus.COMPLETED, AgentStatus.REVOKED]:
return result
# Check heartbeat
if not self.gov.is_alive(agent_id):
result["healthy"] = False
result["issues"].append("HEARTBEAT_TIMEOUT")
# Check lock
if not self.gov.has_lock(agent_id) and state.status == AgentStatus.RUNNING:
result["healthy"] = False
result["issues"].append("LOCK_EXPIRED")
# Check for stuck (no progress)
if state.last_progress_at:
last_progress = datetime.fromisoformat(state.last_progress_at.replace("Z", "+00:00"))
age = (datetime.now(timezone.utc) - last_progress).total_seconds()
if age > self.stuck_threshold:
result["healthy"] = False
result["issues"].append(f"STUCK_{int(age)}s")
return result
def enforce(self, agent_id: str) -> Optional[str]:
"""Enforce health requirements, return action taken"""
check = self.check_agent(agent_id)
if check["healthy"]:
return None
for issue in check["issues"]:
if issue == "HEARTBEAT_TIMEOUT":
self.gov.revoke_agent(agent_id, RevocationType.HEARTBEAT_TIMEOUT,
"No heartbeat received within timeout")
return "REVOKED:HEARTBEAT"
elif issue == "LOCK_EXPIRED":
self.gov.revoke_agent(agent_id, RevocationType.LOCK_EXPIRED,
"Lock expired while agent was running")
return "REVOKED:LOCK"
elif issue.startswith("STUCK_"):
self.gov.revoke_agent(agent_id, RevocationType.HEARTBEAT_TIMEOUT,
f"Agent stuck with no progress: {issue}")
return f"REVOKED:{issue}"
return None
class ComplianceWatcher:
"""
Monitors agent compliance with procedures.
- Checks required artifacts exist
- Detects forbidden actions
- Validates phase transitions
"""
def __init__(self):
self.gov = GovernanceManager()
def check_agent(self, agent_id: str) -> dict:
"""Check an agent's compliance"""
result = {
"agent_id": agent_id,
"compliant": True,
"violations": []
}
state = self.gov.get_agent_state(agent_id)
packet = self.gov.get_instruction_packet(agent_id)
if not state or not packet:
result["compliant"] = False
result["violations"].append("MISSING_STATE_OR_PACKET")
return result
# Check if EXECUTE was entered without PLAN artifact
if state.phase in [AgentPhase.EXECUTE, AgentPhase.VERIFY, AgentPhase.PACKAGE]:
if not self.gov.has_required_artifact(packet.task_id, "plan"):
result["compliant"] = False
result["violations"].append("EXECUTE_WITHOUT_PLAN_ARTIFACT")
# Check required steps from constraints
required_steps = packet.constraints.get("required_steps", [])
# This would need more sophisticated tracking in production
return result
def enforce(self, agent_id: str) -> Optional[str]:
"""Enforce compliance, return action taken"""
check = self.check_agent(agent_id)
if check["compliant"]:
return None
for violation in check["violations"]:
if violation == "EXECUTE_WITHOUT_PLAN_ARTIFACT":
self.gov.revoke_agent(agent_id, RevocationType.PROCEDURE_VIOLATION,
"Attempted EXECUTE phase without plan artifact")
return "REVOKED:NO_PLAN_ARTIFACT"
return None
class VaultLeaseWatcher:
"""
Monitors Vault token/lease validity.
- Checks token accessibility
- Confirms revocation signals
- Enforces token revocation
"""
def __init__(self):
self.gov = GovernanceManager()
self.db = self.gov.db
def set_revocation_signal(self, agent_id: str):
"""Set a signal that this agent should be revoked"""
self.db.set(f"agent:{agent_id}:revoke_signal", "1", ex=300)
def has_revocation_signal(self, agent_id: str) -> bool:
"""Check if revocation signal is set"""
return self.db.exists(f"agent:{agent_id}:revoke_signal")
def clear_revocation_signal(self, agent_id: str):
"""Clear revocation signal after enforcement"""
self.db.delete(f"agent:{agent_id}:revoke_signal")
def enforce(self, agent_id: str) -> Optional[str]:
"""Enforce revocation signal"""
if self.has_revocation_signal(agent_id):
self.gov.revoke_agent(agent_id, RevocationType.MANUAL,
"Revocation signal received")
self.clear_revocation_signal(agent_id)
return "REVOKED:SIGNAL"
return None
class GovernanceMonitorDaemon:
"""
Background daemon that runs all monitors periodically.
"""
def __init__(self, interval: int = 10):
self.interval = interval
self.gov = GovernanceManager()
self.execution_watcher = ExecutionWatcher()
self.compliance_watcher = ComplianceWatcher()
self.vault_watcher = VaultLeaseWatcher()
self.running = False
def get_active_agents(self) -> list[str]:
"""Get list of agents that need monitoring"""
# Get all agent state keys
keys = self.gov.db.keys("agent:*:state")
agents = []
for key in keys:
agent_id = key.split(":")[1]
state = self.gov.get_agent_state(agent_id)
if state and state.status == AgentStatus.RUNNING:
agents.append(agent_id)
return agents
def run_checks(self) -> list[dict]:
"""Run all monitors once"""
results = []
for agent_id in self.get_active_agents():
result = {
"agent_id": agent_id,
"timestamp": datetime.now(timezone.utc).isoformat(),
"actions": []
}
# Execution check
action = self.execution_watcher.enforce(agent_id)
if action:
result["actions"].append(action)
# Compliance check (only if not already revoked)
state = self.gov.get_agent_state(agent_id)
if state and state.status != AgentStatus.REVOKED:
action = self.compliance_watcher.enforce(agent_id)
if action:
result["actions"].append(action)
# Vault check
if state and state.status != AgentStatus.REVOKED:
action = self.vault_watcher.enforce(agent_id)
if action:
result["actions"].append(action)
if result["actions"]:
results.append(result)
return results
def run_daemon(self):
"""Run as a daemon (blocking)"""
self.running = True
print(f"[MONITOR] Starting governance monitor daemon (interval: {self.interval}s)")
while self.running:
results = self.run_checks()
for r in results:
print(f"[MONITOR] {r['agent_id']}: {r['actions']}")
time.sleep(self.interval)
def stop(self):
"""Stop the daemon"""
self.running = False
# =============================================================================
# CLI
# =============================================================================
if __name__ == "__main__":
import sys
if len(sys.argv) < 2:
print("Usage: monitors.py <command>")
print("Commands:")
print(" check <agent_id> - Run all checks on an agent")
print(" daemon - Run monitor daemon")
print(" signal <agent_id> - Send revocation signal")
sys.exit(1)
cmd = sys.argv[1]
if cmd == "check":
agent_id = sys.argv[2]
exec_watch = ExecutionWatcher()
comp_watch = ComplianceWatcher()
print(f"=== Checking Agent: {agent_id} ===\n")
print("Execution Check:")
result = exec_watch.check_agent(agent_id)
print(f" Healthy: {result['healthy']}")
print(f" Issues: {result['issues']}")
print("\nCompliance Check:")
result = comp_watch.check_agent(agent_id)
print(f" Compliant: {result['compliant']}")
print(f" Violations: {result['violations']}")
elif cmd == "daemon":
daemon = GovernanceMonitorDaemon(interval=10)
try:
daemon.run_daemon()
except KeyboardInterrupt:
daemon.stop()
print("\n[MONITOR] Stopped")
elif cmd == "signal":
agent_id = sys.argv[2]
watcher = VaultLeaseWatcher()
watcher.set_revocation_signal(agent_id)
print(f"Revocation signal set for {agent_id}")
else:
print(f"Unknown command: {cmd}")

View File

@ -0,0 +1,12 @@
[project]
name = "llm-planner"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"httpx>=0.28.1",
"openai>=2.15.0",
"pydantic>=2.12.5",
"redis>=7.1.0",
]

396
agents/llm-planner/uv.lock generated Normal file
View File

@ -0,0 +1,396 @@
version = 1
revision = 3
requires-python = ">=3.11"
[[package]]
name = "annotated-types"
version = "0.7.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" },
]
[[package]]
name = "anyio"
version = "4.12.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "idna" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/96/f0/5eb65b2bb0d09ac6776f2eb54adee6abe8228ea05b20a5ad0e4945de8aac/anyio-4.12.1.tar.gz", hash = "sha256:41cfcc3a4c85d3f05c932da7c26d0201ac36f72abd4435ba90d0464a3ffed703", size = 228685, upload-time = "2026-01-06T11:45:21.246Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl", hash = "sha256:d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c", size = 113592, upload-time = "2026-01-06T11:45:19.497Z" },
]
[[package]]
name = "async-timeout"
version = "5.0.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a5/ae/136395dfbfe00dfc94da3f3e136d0b13f394cba8f4841120e34226265780/async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3", size = 9274, upload-time = "2024-11-06T16:41:39.6Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/fe/ba/e2081de779ca30d473f21f5b30e0e737c438205440784c7dfc81efc2b029/async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c", size = 6233, upload-time = "2024-11-06T16:41:37.9Z" },
]
[[package]]
name = "certifi"
version = "2026.1.4"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e0/2d/a891ca51311197f6ad14a7ef42e2399f36cf2f9bd44752b3dc4eab60fdc5/certifi-2026.1.4.tar.gz", hash = "sha256:ac726dd470482006e014ad384921ed6438c457018f4b3d204aea4281258b2120", size = 154268, upload-time = "2026-01-04T02:42:41.825Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e6/ad/3cc14f097111b4de0040c83a525973216457bbeeb63739ef1ed275c1c021/certifi-2026.1.4-py3-none-any.whl", hash = "sha256:9943707519e4add1115f44c2bc244f782c0249876bf51b6599fee1ffbedd685c", size = 152900, upload-time = "2026-01-04T02:42:40.15Z" },
]
[[package]]
name = "colorama"
version = "0.4.6"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" },
]
[[package]]
name = "distro"
version = "1.9.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/fc/f8/98eea607f65de6527f8a2e8885fc8015d3e6f5775df186e443e0964a11c3/distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed", size = 60722, upload-time = "2023-12-24T09:54:32.31Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" },
]
[[package]]
name = "h11"
version = "0.16.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload-time = "2025-04-24T03:35:25.427Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload-time = "2025-04-24T03:35:24.344Z" },
]
[[package]]
name = "httpcore"
version = "1.0.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "h11" },
]
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload-time = "2025-04-24T22:06:22.219Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload-time = "2025-04-24T22:06:20.566Z" },
]
[[package]]
name = "httpx"
version = "0.28.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "certifi" },
{ name = "httpcore" },
{ name = "idna" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload-time = "2024-12-06T15:37:23.222Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload-time = "2024-12-06T15:37:21.509Z" },
]
[[package]]
name = "idna"
version = "3.11"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/6f/6d/0703ccc57f3a7233505399edb88de3cbd678da106337b9fcde432b65ed60/idna-3.11.tar.gz", hash = "sha256:795dafcc9c04ed0c1fb032c2aa73654d8e8c5023a7df64a53f39190ada629902", size = 194582, upload-time = "2025-10-12T14:55:20.501Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" },
]
[[package]]
name = "jiter"
version = "0.12.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/45/9d/e0660989c1370e25848bb4c52d061c71837239738ad937e83edca174c273/jiter-0.12.0.tar.gz", hash = "sha256:64dfcd7d5c168b38d3f9f8bba7fc639edb3418abcc74f22fdbe6b8938293f30b", size = 168294, upload-time = "2025-11-09T20:49:23.302Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/32/f9/eaca4633486b527ebe7e681c431f529b63fe2709e7c5242fc0f43f77ce63/jiter-0.12.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d8f8a7e317190b2c2d60eb2e8aa835270b008139562d70fe732e1c0020ec53c9", size = 316435, upload-time = "2025-11-09T20:47:02.087Z" },
{ url = "https://files.pythonhosted.org/packages/10/c1/40c9f7c22f5e6ff715f28113ebaba27ab85f9af2660ad6e1dd6425d14c19/jiter-0.12.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2218228a077e784c6c8f1a8e5d6b8cb1dea62ce25811c356364848554b2056cd", size = 320548, upload-time = "2025-11-09T20:47:03.409Z" },
{ url = "https://files.pythonhosted.org/packages/6b/1b/efbb68fe87e7711b00d2cfd1f26bb4bfc25a10539aefeaa7727329ffb9cb/jiter-0.12.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9354ccaa2982bf2188fd5f57f79f800ef622ec67beb8329903abf6b10da7d423", size = 351915, upload-time = "2025-11-09T20:47:05.171Z" },
{ url = "https://files.pythonhosted.org/packages/15/2d/c06e659888c128ad1e838123d0638f0efad90cc30860cb5f74dd3f2fc0b3/jiter-0.12.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f2607185ea89b4af9a604d4c7ec40e45d3ad03ee66998b031134bc510232bb7", size = 368966, upload-time = "2025-11-09T20:47:06.508Z" },
{ url = "https://files.pythonhosted.org/packages/6b/20/058db4ae5fb07cf6a4ab2e9b9294416f606d8e467fb74c2184b2a1eeacba/jiter-0.12.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3a585a5e42d25f2e71db5f10b171f5e5ea641d3aa44f7df745aa965606111cc2", size = 482047, upload-time = "2025-11-09T20:47:08.382Z" },
{ url = "https://files.pythonhosted.org/packages/49/bb/dc2b1c122275e1de2eb12905015d61e8316b2f888bdaac34221c301495d6/jiter-0.12.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd9e21d34edff5a663c631f850edcb786719c960ce887a5661e9c828a53a95d9", size = 380835, upload-time = "2025-11-09T20:47:09.81Z" },
{ url = "https://files.pythonhosted.org/packages/23/7d/38f9cd337575349de16da575ee57ddb2d5a64d425c9367f5ef9e4612e32e/jiter-0.12.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a612534770470686cd5431478dc5a1b660eceb410abade6b1b74e320ca98de6", size = 364587, upload-time = "2025-11-09T20:47:11.529Z" },
{ url = "https://files.pythonhosted.org/packages/f0/a3/b13e8e61e70f0bb06085099c4e2462647f53cc2ca97614f7fedcaa2bb9f3/jiter-0.12.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:3985aea37d40a908f887b34d05111e0aae822943796ebf8338877fee2ab67725", size = 390492, upload-time = "2025-11-09T20:47:12.993Z" },
{ url = "https://files.pythonhosted.org/packages/07/71/e0d11422ed027e21422f7bc1883c61deba2d9752b720538430c1deadfbca/jiter-0.12.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b1207af186495f48f72529f8d86671903c8c10127cac6381b11dddc4aaa52df6", size = 522046, upload-time = "2025-11-09T20:47:14.6Z" },
{ url = "https://files.pythonhosted.org/packages/9f/59/b968a9aa7102a8375dbbdfbd2aeebe563c7e5dddf0f47c9ef1588a97e224/jiter-0.12.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ef2fb241de583934c9915a33120ecc06d94aa3381a134570f59eed784e87001e", size = 513392, upload-time = "2025-11-09T20:47:16.011Z" },
{ url = "https://files.pythonhosted.org/packages/ca/e4/7df62002499080dbd61b505c5cb351aa09e9959d176cac2aa8da6f93b13b/jiter-0.12.0-cp311-cp311-win32.whl", hash = "sha256:453b6035672fecce8007465896a25b28a6b59cfe8fbc974b2563a92f5a92a67c", size = 206096, upload-time = "2025-11-09T20:47:17.344Z" },
{ url = "https://files.pythonhosted.org/packages/bb/60/1032b30ae0572196b0de0e87dce3b6c26a1eff71aad5fe43dee3082d32e0/jiter-0.12.0-cp311-cp311-win_amd64.whl", hash = "sha256:ca264b9603973c2ad9435c71a8ec8b49f8f715ab5ba421c85a51cde9887e421f", size = 204899, upload-time = "2025-11-09T20:47:19.365Z" },
{ url = "https://files.pythonhosted.org/packages/49/d5/c145e526fccdb834063fb45c071df78b0cc426bbaf6de38b0781f45d956f/jiter-0.12.0-cp311-cp311-win_arm64.whl", hash = "sha256:cb00ef392e7d684f2754598c02c409f376ddcef857aae796d559e6cacc2d78a5", size = 188070, upload-time = "2025-11-09T20:47:20.75Z" },
{ url = "https://files.pythonhosted.org/packages/92/c9/5b9f7b4983f1b542c64e84165075335e8a236fa9e2ea03a0c79780062be8/jiter-0.12.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:305e061fa82f4680607a775b2e8e0bcb071cd2205ac38e6ef48c8dd5ebe1cf37", size = 314449, upload-time = "2025-11-09T20:47:22.999Z" },
{ url = "https://files.pythonhosted.org/packages/98/6e/e8efa0e78de00db0aee82c0cf9e8b3f2027efd7f8a71f859d8f4be8e98ef/jiter-0.12.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c1860627048e302a528333c9307c818c547f214d8659b0705d2195e1a94b274", size = 319855, upload-time = "2025-11-09T20:47:24.779Z" },
{ url = "https://files.pythonhosted.org/packages/20/26/894cd88e60b5d58af53bec5c6759d1292bd0b37a8b5f60f07abf7a63ae5f/jiter-0.12.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df37577a4f8408f7e0ec3205d2a8f87672af8f17008358063a4d6425b6081ce3", size = 350171, upload-time = "2025-11-09T20:47:26.469Z" },
{ url = "https://files.pythonhosted.org/packages/f5/27/a7b818b9979ac31b3763d25f3653ec3a954044d5e9f5d87f2f247d679fd1/jiter-0.12.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fdd787356c1c13a4f40b43c2156276ef7a71eb487d98472476476d803fb2cf", size = 365590, upload-time = "2025-11-09T20:47:27.918Z" },
{ url = "https://files.pythonhosted.org/packages/ba/7e/e46195801a97673a83746170b17984aa8ac4a455746354516d02ca5541b4/jiter-0.12.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1eb5db8d9c65b112aacf14fcd0faae9913d07a8afea5ed06ccdd12b724e966a1", size = 479462, upload-time = "2025-11-09T20:47:29.654Z" },
{ url = "https://files.pythonhosted.org/packages/ca/75/f833bfb009ab4bd11b1c9406d333e3b4357709ed0570bb48c7c06d78c7dd/jiter-0.12.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73c568cc27c473f82480abc15d1301adf333a7ea4f2e813d6a2c7d8b6ba8d0df", size = 378983, upload-time = "2025-11-09T20:47:31.026Z" },
{ url = "https://files.pythonhosted.org/packages/71/b3/7a69d77943cc837d30165643db753471aff5df39692d598da880a6e51c24/jiter-0.12.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4321e8a3d868919bcb1abb1db550d41f2b5b326f72df29e53b2df8b006eb9403", size = 361328, upload-time = "2025-11-09T20:47:33.286Z" },
{ url = "https://files.pythonhosted.org/packages/b0/ac/a78f90caf48d65ba70d8c6efc6f23150bc39dc3389d65bbec2a95c7bc628/jiter-0.12.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0a51bad79f8cc9cac2b4b705039f814049142e0050f30d91695a2d9a6611f126", size = 386740, upload-time = "2025-11-09T20:47:34.703Z" },
{ url = "https://files.pythonhosted.org/packages/39/b6/5d31c2cc8e1b6a6bcf3c5721e4ca0a3633d1ab4754b09bc7084f6c4f5327/jiter-0.12.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:2a67b678f6a5f1dd6c36d642d7db83e456bc8b104788262aaefc11a22339f5a9", size = 520875, upload-time = "2025-11-09T20:47:36.058Z" },
{ url = "https://files.pythonhosted.org/packages/30/b5/4df540fae4e9f68c54b8dab004bd8c943a752f0b00efd6e7d64aa3850339/jiter-0.12.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efe1a211fe1fd14762adea941e3cfd6c611a136e28da6c39272dbb7a1bbe6a86", size = 511457, upload-time = "2025-11-09T20:47:37.932Z" },
{ url = "https://files.pythonhosted.org/packages/07/65/86b74010e450a1a77b2c1aabb91d4a91dd3cd5afce99f34d75fd1ac64b19/jiter-0.12.0-cp312-cp312-win32.whl", hash = "sha256:d779d97c834b4278276ec703dc3fc1735fca50af63eb7262f05bdb4e62203d44", size = 204546, upload-time = "2025-11-09T20:47:40.47Z" },
{ url = "https://files.pythonhosted.org/packages/1c/c7/6659f537f9562d963488e3e55573498a442503ced01f7e169e96a6110383/jiter-0.12.0-cp312-cp312-win_amd64.whl", hash = "sha256:e8269062060212b373316fe69236096aaf4c49022d267c6736eebd66bbbc60bb", size = 205196, upload-time = "2025-11-09T20:47:41.794Z" },
{ url = "https://files.pythonhosted.org/packages/21/f4/935304f5169edadfec7f9c01eacbce4c90bb9a82035ac1de1f3bd2d40be6/jiter-0.12.0-cp312-cp312-win_arm64.whl", hash = "sha256:06cb970936c65de926d648af0ed3d21857f026b1cf5525cb2947aa5e01e05789", size = 186100, upload-time = "2025-11-09T20:47:43.007Z" },
{ url = "https://files.pythonhosted.org/packages/3d/a6/97209693b177716e22576ee1161674d1d58029eb178e01866a0422b69224/jiter-0.12.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:6cc49d5130a14b732e0612bc76ae8db3b49898732223ef8b7599aa8d9810683e", size = 313658, upload-time = "2025-11-09T20:47:44.424Z" },
{ url = "https://files.pythonhosted.org/packages/06/4d/125c5c1537c7d8ee73ad3d530a442d6c619714b95027143f1b61c0b4dfe0/jiter-0.12.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:37f27a32ce36364d2fa4f7fdc507279db604d27d239ea2e044c8f148410defe1", size = 318605, upload-time = "2025-11-09T20:47:45.973Z" },
{ url = "https://files.pythonhosted.org/packages/99/bf/a840b89847885064c41a5f52de6e312e91fa84a520848ee56c97e4fa0205/jiter-0.12.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bbc0944aa3d4b4773e348cda635252824a78f4ba44328e042ef1ff3f6080d1cf", size = 349803, upload-time = "2025-11-09T20:47:47.535Z" },
{ url = "https://files.pythonhosted.org/packages/8a/88/e63441c28e0db50e305ae23e19c1d8fae012d78ed55365da392c1f34b09c/jiter-0.12.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:da25c62d4ee1ffbacb97fac6dfe4dcd6759ebdc9015991e92a6eae5816287f44", size = 365120, upload-time = "2025-11-09T20:47:49.284Z" },
{ url = "https://files.pythonhosted.org/packages/0a/7c/49b02714af4343970eb8aca63396bc1c82fa01197dbb1e9b0d274b550d4e/jiter-0.12.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:048485c654b838140b007390b8182ba9774621103bd4d77c9c3f6f117474ba45", size = 479918, upload-time = "2025-11-09T20:47:50.807Z" },
{ url = "https://files.pythonhosted.org/packages/69/ba/0a809817fdd5a1db80490b9150645f3aae16afad166960bcd562be194f3b/jiter-0.12.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:635e737fbb7315bef0037c19b88b799143d2d7d3507e61a76751025226b3ac87", size = 379008, upload-time = "2025-11-09T20:47:52.211Z" },
{ url = "https://files.pythonhosted.org/packages/5f/c3/c9fc0232e736c8877d9e6d83d6eeb0ba4e90c6c073835cc2e8f73fdeef51/jiter-0.12.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e017c417b1ebda911bd13b1e40612704b1f5420e30695112efdbed8a4b389ed", size = 361785, upload-time = "2025-11-09T20:47:53.512Z" },
{ url = "https://files.pythonhosted.org/packages/96/61/61f69b7e442e97ca6cd53086ddc1cf59fb830549bc72c0a293713a60c525/jiter-0.12.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:89b0bfb8b2bf2351fba36bb211ef8bfceba73ef58e7f0c68fb67b5a2795ca2f9", size = 386108, upload-time = "2025-11-09T20:47:54.893Z" },
{ url = "https://files.pythonhosted.org/packages/e9/2e/76bb3332f28550c8f1eba3bf6e5efe211efda0ddbbaf24976bc7078d42a5/jiter-0.12.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:f5aa5427a629a824a543672778c9ce0c5e556550d1569bb6ea28a85015287626", size = 519937, upload-time = "2025-11-09T20:47:56.253Z" },
{ url = "https://files.pythonhosted.org/packages/84/d6/fa96efa87dc8bff2094fb947f51f66368fa56d8d4fc9e77b25d7fbb23375/jiter-0.12.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ed53b3d6acbcb0fd0b90f20c7cb3b24c357fe82a3518934d4edfa8c6898e498c", size = 510853, upload-time = "2025-11-09T20:47:58.32Z" },
{ url = "https://files.pythonhosted.org/packages/8a/28/93f67fdb4d5904a708119a6ab58a8f1ec226ff10a94a282e0215402a8462/jiter-0.12.0-cp313-cp313-win32.whl", hash = "sha256:4747de73d6b8c78f2e253a2787930f4fffc68da7fa319739f57437f95963c4de", size = 204699, upload-time = "2025-11-09T20:47:59.686Z" },
{ url = "https://files.pythonhosted.org/packages/c4/1f/30b0eb087045a0abe2a5c9c0c0c8da110875a1d3be83afd4a9a4e548be3c/jiter-0.12.0-cp313-cp313-win_amd64.whl", hash = "sha256:e25012eb0c456fcc13354255d0338cd5397cce26c77b2832b3c4e2e255ea5d9a", size = 204258, upload-time = "2025-11-09T20:48:01.01Z" },
{ url = "https://files.pythonhosted.org/packages/2c/f4/2b4daf99b96bce6fc47971890b14b2a36aef88d7beb9f057fafa032c6141/jiter-0.12.0-cp313-cp313-win_arm64.whl", hash = "sha256:c97b92c54fe6110138c872add030a1f99aea2401ddcdaa21edf74705a646dd60", size = 185503, upload-time = "2025-11-09T20:48:02.35Z" },
{ url = "https://files.pythonhosted.org/packages/39/ca/67bb15a7061d6fe20b9b2a2fd783e296a1e0f93468252c093481a2f00efa/jiter-0.12.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:53839b35a38f56b8be26a7851a48b89bc47e5d88e900929df10ed93b95fea3d6", size = 317965, upload-time = "2025-11-09T20:48:03.783Z" },
{ url = "https://files.pythonhosted.org/packages/18/af/1788031cd22e29c3b14bc6ca80b16a39a0b10e611367ffd480c06a259831/jiter-0.12.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94f669548e55c91ab47fef8bddd9c954dab1938644e715ea49d7e117015110a4", size = 345831, upload-time = "2025-11-09T20:48:05.55Z" },
{ url = "https://files.pythonhosted.org/packages/05/17/710bf8472d1dff0d3caf4ced6031060091c1320f84ee7d5dcbed1f352417/jiter-0.12.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:351d54f2b09a41600ffea43d081522d792e81dcfb915f6d2d242744c1cc48beb", size = 361272, upload-time = "2025-11-09T20:48:06.951Z" },
{ url = "https://files.pythonhosted.org/packages/fb/f1/1dcc4618b59761fef92d10bcbb0b038b5160be653b003651566a185f1a5c/jiter-0.12.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2a5e90604620f94bf62264e7c2c038704d38217b7465b863896c6d7c902b06c7", size = 204604, upload-time = "2025-11-09T20:48:08.328Z" },
{ url = "https://files.pythonhosted.org/packages/d9/32/63cb1d9f1c5c6632a783c0052cde9ef7ba82688f7065e2f0d5f10a7e3edb/jiter-0.12.0-cp313-cp313t-win_arm64.whl", hash = "sha256:88ef757017e78d2860f96250f9393b7b577b06a956ad102c29c8237554380db3", size = 185628, upload-time = "2025-11-09T20:48:09.572Z" },
{ url = "https://files.pythonhosted.org/packages/a8/99/45c9f0dbe4a1416b2b9a8a6d1236459540f43d7fb8883cff769a8db0612d/jiter-0.12.0-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:c46d927acd09c67a9fb1416df45c5a04c27e83aae969267e98fba35b74e99525", size = 312478, upload-time = "2025-11-09T20:48:10.898Z" },
{ url = "https://files.pythonhosted.org/packages/4c/a7/54ae75613ba9e0f55fcb0bc5d1f807823b5167cc944e9333ff322e9f07dd/jiter-0.12.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:774ff60b27a84a85b27b88cd5583899c59940bcc126caca97eb2a9df6aa00c49", size = 318706, upload-time = "2025-11-09T20:48:12.266Z" },
{ url = "https://files.pythonhosted.org/packages/59/31/2aa241ad2c10774baf6c37f8b8e1f39c07db358f1329f4eb40eba179c2a2/jiter-0.12.0-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5433fab222fb072237df3f637d01b81f040a07dcac1cb4a5c75c7aa9ed0bef1", size = 351894, upload-time = "2025-11-09T20:48:13.673Z" },
{ url = "https://files.pythonhosted.org/packages/54/4f/0f2759522719133a9042781b18cc94e335b6d290f5e2d3e6899d6af933e3/jiter-0.12.0-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:f8c593c6e71c07866ec6bfb790e202a833eeec885022296aff6b9e0b92d6a70e", size = 365714, upload-time = "2025-11-09T20:48:15.083Z" },
{ url = "https://files.pythonhosted.org/packages/dc/6f/806b895f476582c62a2f52c453151edd8a0fde5411b0497baaa41018e878/jiter-0.12.0-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:90d32894d4c6877a87ae00c6b915b609406819dce8bc0d4e962e4de2784e567e", size = 478989, upload-time = "2025-11-09T20:48:16.706Z" },
{ url = "https://files.pythonhosted.org/packages/86/6c/012d894dc6e1033acd8db2b8346add33e413ec1c7c002598915278a37f79/jiter-0.12.0-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:798e46eed9eb10c3adbbacbd3bdb5ecd4cf7064e453d00dbef08802dae6937ff", size = 378615, upload-time = "2025-11-09T20:48:18.614Z" },
{ url = "https://files.pythonhosted.org/packages/87/30/d718d599f6700163e28e2c71c0bbaf6dace692e7df2592fd793ac9276717/jiter-0.12.0-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3f1368f0a6719ea80013a4eb90ba72e75d7ea67cfc7846db2ca504f3df0169a", size = 364745, upload-time = "2025-11-09T20:48:20.117Z" },
{ url = "https://files.pythonhosted.org/packages/8f/85/315b45ce4b6ddc7d7fceca24068543b02bdc8782942f4ee49d652e2cc89f/jiter-0.12.0-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:65f04a9d0b4406f7e51279710b27484af411896246200e461d80d3ba0caa901a", size = 386502, upload-time = "2025-11-09T20:48:21.543Z" },
{ url = "https://files.pythonhosted.org/packages/74/0b/ce0434fb40c5b24b368fe81b17074d2840748b4952256bab451b72290a49/jiter-0.12.0-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:fd990541982a24281d12b67a335e44f117e4c6cbad3c3b75c7dea68bf4ce3a67", size = 519845, upload-time = "2025-11-09T20:48:22.964Z" },
{ url = "https://files.pythonhosted.org/packages/e8/a3/7a7a4488ba052767846b9c916d208b3ed114e3eb670ee984e4c565b9cf0d/jiter-0.12.0-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:b111b0e9152fa7df870ecaebb0bd30240d9f7fff1f2003bcb4ed0f519941820b", size = 510701, upload-time = "2025-11-09T20:48:24.483Z" },
{ url = "https://files.pythonhosted.org/packages/c3/16/052ffbf9d0467b70af24e30f91e0579e13ded0c17bb4a8eb2aed3cb60131/jiter-0.12.0-cp314-cp314-win32.whl", hash = "sha256:a78befb9cc0a45b5a5a0d537b06f8544c2ebb60d19d02c41ff15da28a9e22d42", size = 205029, upload-time = "2025-11-09T20:48:25.749Z" },
{ url = "https://files.pythonhosted.org/packages/e4/18/3cf1f3f0ccc789f76b9a754bdb7a6977e5d1d671ee97a9e14f7eb728d80e/jiter-0.12.0-cp314-cp314-win_amd64.whl", hash = "sha256:e1fe01c082f6aafbe5c8faf0ff074f38dfb911d53f07ec333ca03f8f6226debf", size = 204960, upload-time = "2025-11-09T20:48:27.415Z" },
{ url = "https://files.pythonhosted.org/packages/02/68/736821e52ecfdeeb0f024b8ab01b5a229f6b9293bbdb444c27efade50b0f/jiter-0.12.0-cp314-cp314-win_arm64.whl", hash = "sha256:d72f3b5a432a4c546ea4bedc84cce0c3404874f1d1676260b9c7f048a9855451", size = 185529, upload-time = "2025-11-09T20:48:29.125Z" },
{ url = "https://files.pythonhosted.org/packages/30/61/12ed8ee7a643cce29ac97c2281f9ce3956eb76b037e88d290f4ed0d41480/jiter-0.12.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e6ded41aeba3603f9728ed2b6196e4df875348ab97b28fc8afff115ed42ba7a7", size = 318974, upload-time = "2025-11-09T20:48:30.87Z" },
{ url = "https://files.pythonhosted.org/packages/2d/c6/f3041ede6d0ed5e0e79ff0de4c8f14f401bbf196f2ef3971cdbe5fd08d1d/jiter-0.12.0-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a947920902420a6ada6ad51892082521978e9dd44a802663b001436e4b771684", size = 345932, upload-time = "2025-11-09T20:48:32.658Z" },
{ url = "https://files.pythonhosted.org/packages/d5/5d/4d94835889edd01ad0e2dbfc05f7bdfaed46292e7b504a6ac7839aa00edb/jiter-0.12.0-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:add5e227e0554d3a52cf390a7635edaffdf4f8fce4fdbcef3cc2055bb396a30c", size = 367243, upload-time = "2025-11-09T20:48:34.093Z" },
{ url = "https://files.pythonhosted.org/packages/fd/76/0051b0ac2816253a99d27baf3dda198663aff882fa6ea7deeb94046da24e/jiter-0.12.0-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f9b1cda8fcb736250d7e8711d4580ebf004a46771432be0ae4796944b5dfa5d", size = 479315, upload-time = "2025-11-09T20:48:35.507Z" },
{ url = "https://files.pythonhosted.org/packages/70/ae/83f793acd68e5cb24e483f44f482a1a15601848b9b6f199dacb970098f77/jiter-0.12.0-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:deeb12a2223fe0135c7ff1356a143d57f95bbf1f4a66584f1fc74df21d86b993", size = 380714, upload-time = "2025-11-09T20:48:40.014Z" },
{ url = "https://files.pythonhosted.org/packages/b1/5e/4808a88338ad2c228b1126b93fcd8ba145e919e886fe910d578230dabe3b/jiter-0.12.0-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c596cc0f4cb574877550ce4ecd51f8037469146addd676d7c1a30ebe6391923f", size = 365168, upload-time = "2025-11-09T20:48:41.462Z" },
{ url = "https://files.pythonhosted.org/packages/0c/d4/04619a9e8095b42aef436b5aeb4c0282b4ff1b27d1db1508df9f5dc82750/jiter-0.12.0-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ab4c823b216a4aeab3fdbf579c5843165756bd9ad87cc6b1c65919c4715f783", size = 387893, upload-time = "2025-11-09T20:48:42.921Z" },
{ url = "https://files.pythonhosted.org/packages/17/ea/d3c7e62e4546fdc39197fa4a4315a563a89b95b6d54c0d25373842a59cbe/jiter-0.12.0-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:e427eee51149edf962203ff8db75a7514ab89be5cb623fb9cea1f20b54f1107b", size = 520828, upload-time = "2025-11-09T20:48:44.278Z" },
{ url = "https://files.pythonhosted.org/packages/cc/0b/c6d3562a03fd767e31cb119d9041ea7958c3c80cb3d753eafb19b3b18349/jiter-0.12.0-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:edb868841f84c111255ba5e80339d386d937ec1fdce419518ce1bd9370fac5b6", size = 511009, upload-time = "2025-11-09T20:48:45.726Z" },
{ url = "https://files.pythonhosted.org/packages/aa/51/2cb4468b3448a8385ebcd15059d325c9ce67df4e2758d133ab9442b19834/jiter-0.12.0-cp314-cp314t-win32.whl", hash = "sha256:8bbcfe2791dfdb7c5e48baf646d37a6a3dcb5a97a032017741dea9f817dca183", size = 205110, upload-time = "2025-11-09T20:48:47.033Z" },
{ url = "https://files.pythonhosted.org/packages/b2/c5/ae5ec83dec9c2d1af805fd5fe8f74ebded9c8670c5210ec7820ce0dbeb1e/jiter-0.12.0-cp314-cp314t-win_amd64.whl", hash = "sha256:2fa940963bf02e1d8226027ef461e36af472dea85d36054ff835aeed944dd873", size = 205223, upload-time = "2025-11-09T20:48:49.076Z" },
{ url = "https://files.pythonhosted.org/packages/97/9a/3c5391907277f0e55195550cf3fa8e293ae9ee0c00fb402fec1e38c0c82f/jiter-0.12.0-cp314-cp314t-win_arm64.whl", hash = "sha256:506c9708dd29b27288f9f8f1140c3cb0e3d8ddb045956d7757b1fa0e0f39a473", size = 185564, upload-time = "2025-11-09T20:48:50.376Z" },
{ url = "https://files.pythonhosted.org/packages/fe/54/5339ef1ecaa881c6948669956567a64d2670941925f245c434f494ffb0e5/jiter-0.12.0-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:4739a4657179ebf08f85914ce50332495811004cc1747852e8b2041ed2aab9b8", size = 311144, upload-time = "2025-11-09T20:49:10.503Z" },
{ url = "https://files.pythonhosted.org/packages/27/74/3446c652bffbd5e81ab354e388b1b5fc1d20daac34ee0ed11ff096b1b01a/jiter-0.12.0-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:41da8def934bf7bec16cb24bd33c0ca62126d2d45d81d17b864bd5ad721393c3", size = 305877, upload-time = "2025-11-09T20:49:12.269Z" },
{ url = "https://files.pythonhosted.org/packages/a1/f4/ed76ef9043450f57aac2d4fbeb27175aa0eb9c38f833be6ef6379b3b9a86/jiter-0.12.0-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c44ee814f499c082e69872d426b624987dbc5943ab06e9bbaa4f81989fdb79e", size = 340419, upload-time = "2025-11-09T20:49:13.803Z" },
{ url = "https://files.pythonhosted.org/packages/21/01/857d4608f5edb0664aa791a3d45702e1a5bcfff9934da74035e7b9803846/jiter-0.12.0-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd2097de91cf03eaa27b3cbdb969addf83f0179c6afc41bbc4513705e013c65d", size = 347212, upload-time = "2025-11-09T20:49:15.643Z" },
{ url = "https://files.pythonhosted.org/packages/cb/f5/12efb8ada5f5c9edc1d4555fe383c1fb2eac05ac5859258a72d61981d999/jiter-0.12.0-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:e8547883d7b96ef2e5fe22b88f8a4c8725a56e7f4abafff20fd5272d634c7ecb", size = 309974, upload-time = "2025-11-09T20:49:17.187Z" },
{ url = "https://files.pythonhosted.org/packages/85/15/d6eb3b770f6a0d332675141ab3962fd4a7c270ede3515d9f3583e1d28276/jiter-0.12.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:89163163c0934854a668ed783a2546a0617f71706a2551a4a0666d91ab365d6b", size = 304233, upload-time = "2025-11-09T20:49:18.734Z" },
{ url = "https://files.pythonhosted.org/packages/8c/3e/e7e06743294eea2cf02ced6aa0ff2ad237367394e37a0e2b4a1108c67a36/jiter-0.12.0-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d96b264ab7d34bbb2312dedc47ce07cd53f06835eacbc16dde3761f47c3a9e7f", size = 338537, upload-time = "2025-11-09T20:49:20.317Z" },
{ url = "https://files.pythonhosted.org/packages/2f/9c/6753e6522b8d0ef07d3a3d239426669e984fb0eba15a315cdbc1253904e4/jiter-0.12.0-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c24e864cb30ab82311c6425655b0cdab0a98c5d973b065c66a3f020740c2324c", size = 346110, upload-time = "2025-11-09T20:49:21.817Z" },
]
[[package]]
name = "llm-planner"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "httpx" },
{ name = "openai" },
{ name = "pydantic" },
{ name = "redis" },
]
[package.metadata]
requires-dist = [
{ name = "httpx", specifier = ">=0.28.1" },
{ name = "openai", specifier = ">=2.15.0" },
{ name = "pydantic", specifier = ">=2.12.5" },
{ name = "redis", specifier = ">=7.1.0" },
]
[[package]]
name = "openai"
version = "2.15.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "distro" },
{ name = "httpx" },
{ name = "jiter" },
{ name = "pydantic" },
{ name = "sniffio" },
{ name = "tqdm" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/94/f4/4690ecb5d70023ce6bfcfeabfe717020f654bde59a775058ec6ac4692463/openai-2.15.0.tar.gz", hash = "sha256:42eb8cbb407d84770633f31bf727d4ffb4138711c670565a41663d9439174fba", size = 627383, upload-time = "2026-01-09T22:10:08.603Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b5/df/c306f7375d42bafb379934c2df4c2fa3964656c8c782bac75ee10c102818/openai-2.15.0-py3-none-any.whl", hash = "sha256:6ae23b932cd7230f7244e52954daa6602716d6b9bf235401a107af731baea6c3", size = 1067879, upload-time = "2026-01-09T22:10:06.446Z" },
]
[[package]]
name = "pydantic"
version = "2.12.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "annotated-types" },
{ name = "pydantic-core" },
{ name = "typing-extensions" },
{ name = "typing-inspection" },
]
sdist = { url = "https://files.pythonhosted.org/packages/69/44/36f1a6e523abc58ae5f928898e4aca2e0ea509b5aa6f6f392a5d882be928/pydantic-2.12.5.tar.gz", hash = "sha256:4d351024c75c0f085a9febbb665ce8c0c6ec5d30e903bdb6394b7ede26aebb49", size = 821591, upload-time = "2025-11-26T15:11:46.471Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5a/87/b70ad306ebb6f9b585f114d0ac2137d792b48be34d732d60e597c2f8465a/pydantic-2.12.5-py3-none-any.whl", hash = "sha256:e561593fccf61e8a20fc46dfc2dfe075b8be7d0188df33f221ad1f0139180f9d", size = 463580, upload-time = "2025-11-26T15:11:44.605Z" },
]
[[package]]
name = "pydantic-core"
version = "2.41.5"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" },
{ url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" },
{ url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" },
{ url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" },
{ url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" },
{ url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" },
{ url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" },
{ url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" },
{ url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" },
{ url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" },
{ url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" },
{ url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" },
{ url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" },
{ url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" },
{ url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" },
{ url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" },
{ url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" },
{ url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" },
{ url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" },
{ url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" },
{ url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" },
{ url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" },
{ url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" },
{ url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" },
{ url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" },
{ url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" },
{ url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" },
{ url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" },
{ url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" },
{ url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" },
{ url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" },
{ url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" },
{ url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" },
{ url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" },
{ url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" },
{ url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" },
{ url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" },
{ url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" },
{ url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" },
{ url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" },
{ url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" },
{ url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" },
{ url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" },
{ url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" },
{ url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" },
{ url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" },
{ url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" },
{ url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" },
{ url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" },
{ url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" },
{ url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" },
{ url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" },
{ url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" },
{ url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" },
{ url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" },
{ url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" },
{ url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" },
{ url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" },
{ url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" },
{ url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" },
{ url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" },
{ url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" },
{ url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" },
{ url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" },
{ url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" },
{ url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" },
{ url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" },
{ url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" },
{ url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" },
{ url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" },
{ url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" },
{ url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" },
{ url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" },
{ url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" },
{ url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" },
{ url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" },
{ url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" },
{ url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" },
{ url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" },
{ url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" },
{ url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" },
{ url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" },
{ url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" },
{ url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" },
{ url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" },
{ url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" },
]
[[package]]
name = "redis"
version = "7.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "async-timeout", marker = "python_full_version < '3.11.3'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/43/c8/983d5c6579a411d8a99bc5823cc5712768859b5ce2c8afe1a65b37832c81/redis-7.1.0.tar.gz", hash = "sha256:b1cc3cfa5a2cb9c2ab3ba700864fb0ad75617b41f01352ce5779dabf6d5f9c3c", size = 4796669, upload-time = "2025-11-19T15:54:39.961Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/89/f0/8956f8a86b20d7bb9d6ac0187cf4cd54d8065bc9a1a09eb8011d4d326596/redis-7.1.0-py3-none-any.whl", hash = "sha256:23c52b208f92b56103e17c5d06bdc1a6c2c0b3106583985a76a18f83b265de2b", size = 354159, upload-time = "2025-11-19T15:54:38.064Z" },
]
[[package]]
name = "sniffio"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload-time = "2024-02-25T23:20:04.057Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
]
[[package]]
name = "tqdm"
version = "4.67.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "colorama", marker = "sys_platform == 'win32'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" },
]
[[package]]
name = "typing-extensions"
version = "4.15.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" },
]
[[package]]
name = "typing-inspection"
version = "0.4.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/55/e3/70399cb7dd41c10ac53367ae42139cf4b1ca5f36bb3dc6c9d33acdb43655/typing_inspection-0.4.2.tar.gz", hash = "sha256:ba561c48a67c5958007083d386c3295464928b01faa735ab8547c5692e87f464", size = 75949, upload-time = "2025-10-01T02:14:41.687Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/dc/9b/47798a6c91d8bdb567fe2698fe81e0c6b7cb7ef4d13da4114b41d239f65d/typing_inspection-0.4.2-py3-none-any.whl", hash = "sha256:4ed1cacbdc298c220f1bd249ed5287caa16f34d44ef4e9c3d0cbad5b521545e7", size = 14611, upload-time = "2025-10-01T02:14:40.154Z" },
]

View File

@ -0,0 +1,36 @@
# Multi Agent
> Multi-agent coordination demos
## Overview
This directory contains multi-agent coordination demos.
## Key Files
| File | Description |
|------|-------------|
| `types.ts` | TypeScript module |
| `coordination.ts` | TypeScript module |
| `agents.ts` | TypeScript module |
| `orchestrator.ts` | TypeScript module |
| `package.json` | Data/Schema |
## Interfaces / APIs
*Document any APIs, CLI commands, or interfaces here.*
## Status
** Not Started**
See [STATUS.md](./STATUS.md) for detailed progress tracking.
## Architecture Reference
Part of the [Agent Governance System](/opt/agent-governance/docs/ARCHITECTURE.md).
Parent: [Agents](..)
---
*Last updated: 2026-01-23 23:25:09 UTC*

View File

@ -0,0 +1,30 @@
# Status: Multi Agent
## 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*

View File

@ -0,0 +1,893 @@
/**
* Multi-Agent Coordination System - Agent Implementations
*/
import OpenAI from "openai";
import { $ } from "bun";
import type {
AgentRole,
AgentState,
AgentMessage,
TaskDefinition,
BlackboardEntry,
ConsensusVote,
} from "./types";
import {
Blackboard,
MessageBus,
AgentStateManager,
SpawnController,
MetricsCollector,
} from "./coordination";
function now(): string {
return new Date().toISOString();
}
function generateId(): string {
return Math.random().toString(36).slice(2, 10) + Date.now().toString(36);
}
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;
}
// =============================================================================
// Base Agent Class
// =============================================================================
abstract class BaseAgent {
protected role: AgentRole;
protected taskId: string;
protected blackboard: Blackboard;
protected messageBus: MessageBus;
protected stateManager: AgentStateManager;
protected metrics: MetricsCollector;
protected llm!: OpenAI;
protected model: string;
protected state: AgentState;
protected startTime: number;
protected log: (msg: string) => void;
constructor(
role: AgentRole,
taskId: string,
blackboard: Blackboard,
messageBus: MessageBus,
stateManager: AgentStateManager,
metrics: MetricsCollector,
model: string = "anthropic/claude-sonnet-4"
) {
this.role = role;
this.taskId = taskId;
this.blackboard = blackboard;
this.messageBus = messageBus;
this.stateManager = stateManager;
this.metrics = metrics;
this.model = model;
this.startTime = Date.now();
this.state = {
agent_id: `${role}-${taskId}`,
role,
status: "IDLE",
current_task: "",
progress: 0,
confidence: 0,
last_activity: now(),
messages_sent: 0,
messages_received: 0,
proposals_made: 0,
conflicts_detected: 0,
};
this.log = (msg: string) => {
const elapsed = ((Date.now() - this.startTime) / 1000).toFixed(1);
console.log(`[${elapsed}s] [${this.role}] ${msg}`);
};
}
async init(): Promise<void> {
const secrets = await getVaultSecret("api-keys/openrouter");
this.llm = new OpenAI({
baseURL: "https://openrouter.ai/api/v1",
apiKey: secrets.api_key,
});
// Register message handler
this.messageBus.onMessage(this.role, (msg) => this.handleMessage(msg));
await this.updateState({ status: "IDLE" });
this.log("Initialized");
}
protected async updateState(partial: Partial<AgentState>): Promise<void> {
Object.assign(this.state, partial);
this.state.last_activity = now();
await this.stateManager.updateState(this.state);
}
protected async handleMessage(msg: AgentMessage): Promise<void> {
this.state.messages_received++;
this.log(`Received ${msg.type} from ${msg.from}`);
// Override in subclasses for specific handling
}
protected async sendMessage(to: AgentRole | "ALL", type: AgentMessage["type"], payload: any, correlationId?: string): Promise<void> {
await this.messageBus.send(to, type, payload, correlationId);
this.state.messages_sent++;
await this.updateState({});
}
protected async writeToBlackboard(section: Parameters<Blackboard["write"]>[0], key: string, value: any): Promise<void> {
await this.blackboard.write(section, key, value, this.role);
this.log(`Wrote to blackboard: ${section}/${key}`);
}
protected async readFromBlackboard(section: Parameters<Blackboard["read"]>[0], key: string): Promise<any> {
const entry = await this.blackboard.read(section, key);
return entry?.value;
}
protected async callLLM(systemPrompt: string, userPrompt: string, maxTokens: number = 2000): Promise<string> {
const response = await this.llm.chat.completions.create({
model: this.model,
messages: [
{ role: "system", content: systemPrompt },
{ role: "user", content: userPrompt },
],
max_tokens: maxTokens,
temperature: 0.4,
});
return response.choices[0].message.content || "";
}
abstract run(task: TaskDefinition): Promise<void>;
}
// =============================================================================
// Agent ALPHA - Research & Analysis Specialist
// =============================================================================
export class AgentAlpha extends BaseAgent {
private proposals: Map<string, any> = new Map();
constructor(
taskId: string,
blackboard: Blackboard,
messageBus: MessageBus,
stateManager: AgentStateManager,
metrics: MetricsCollector,
model?: string
) {
super("ALPHA", taskId, blackboard, messageBus, stateManager, metrics, model);
}
protected async handleMessage(msg: AgentMessage): Promise<void> {
await super.handleMessage(msg);
switch (msg.type) {
case "FEEDBACK":
await this.handleFeedback(msg);
break;
case "QUERY":
await this.handleQuery(msg);
break;
case "SYNC":
await this.handleSync(msg);
break;
}
}
private async handleFeedback(msg: AgentMessage): Promise<void> {
const { proposal_id, accepted, reasoning, suggestions } = msg.payload;
this.log(`Received feedback on proposal ${proposal_id}: ${accepted ? "ACCEPTED" : "REJECTED"}`);
if (!accepted && suggestions) {
// Revise proposal based on feedback
const original = this.proposals.get(proposal_id);
if (original) {
await this.reviseProposal(proposal_id, original, suggestions);
}
}
}
private async handleQuery(msg: AgentMessage): Promise<void> {
const { question, context } = msg.payload;
this.log(`Answering query from ${msg.from}: ${question.slice(0, 50)}...`);
const answer = await this.analyzeQuestion(question, context);
await this.sendMessage(msg.from as AgentRole, "RESPONSE", {
question,
answer,
confidence: answer.confidence,
}, msg.id);
}
private async handleSync(msg: AgentMessage): Promise<void> {
// Share current state with other agents
const currentProposals = Array.from(this.proposals.entries());
await this.sendMessage(msg.from as AgentRole, "RESPONSE", {
proposals: currentProposals,
progress: this.state.progress,
current_task: this.state.current_task,
}, msg.id);
}
private async analyzeQuestion(question: string, context: any): Promise<any> {
const response = await this.callLLM(
`You are Agent ALPHA, a research and analysis specialist. Answer questions thoroughly and provide confidence scores.
Output JSON: { "answer": "...", "confidence": 0.0-1.0, "supporting_evidence": [...], "uncertainties": [...] }`,
`Question: ${question}\nContext: ${JSON.stringify(context)}`
);
try {
const match = response.match(/\{[\s\S]*\}/);
return match ? JSON.parse(match[0]) : { answer: response, confidence: 0.5 };
} catch {
return { answer: response, confidence: 0.5 };
}
}
private async reviseProposal(proposalId: string, original: any, suggestions: string[]): Promise<void> {
this.log(`Revising proposal ${proposalId} based on feedback`);
const response = await this.callLLM(
`You are Agent ALPHA. Revise the proposal based on the feedback suggestions.
Output JSON: { "revised_proposal": {...}, "changes_made": [...], "confidence": 0.0-1.0 }`,
`Original proposal: ${JSON.stringify(original)}\nSuggestions: ${suggestions.join(", ")}`
);
try {
const match = response.match(/\{[\s\S]*\}/);
if (match) {
const revised = JSON.parse(match[0]);
const newProposalId = proposalId + "-rev";
this.proposals.set(newProposalId, revised.revised_proposal);
await this.writeToBlackboard("solutions", newProposalId, revised);
await this.sendMessage("BETA", "PROPOSAL", {
proposal_id: newProposalId,
proposal: revised.revised_proposal,
is_revision: true,
original_id: proposalId,
});
this.state.proposals_made++;
}
} catch (e) {
this.log(`Failed to revise proposal: ${e}`);
}
}
async run(task: TaskDefinition): Promise<void> {
await this.updateState({ status: "WORKING", current_task: "Analyzing problem" });
this.log(`Starting analysis of: ${task.objective}`);
// Phase 1: Problem Analysis
await this.writeToBlackboard("problem", "objective", task.objective);
await this.writeToBlackboard("problem", "constraints", task.constraints);
const analysis = await this.analyzeProblem(task);
await this.writeToBlackboard("problem", "analysis", analysis);
this.log(`Problem analysis complete. Complexity: ${analysis.complexity_score}`);
await this.updateState({ progress: 0.2 });
// Phase 2: Generate Initial Proposals
await this.updateState({ current_task: "Generating solution proposals" });
const proposals = await this.generateProposals(task, analysis);
for (const proposal of proposals) {
const proposalId = generateId();
this.proposals.set(proposalId, proposal);
await this.writeToBlackboard("solutions", proposalId, proposal);
// Send proposal to BETA for evaluation
await this.sendMessage("BETA", "PROPOSAL", {
proposal_id: proposalId,
proposal,
phase: "initial",
});
this.state.proposals_made++;
}
this.log(`Generated ${proposals.length} initial proposals`);
await this.updateState({ progress: 0.5 });
// Phase 3: Iterative Refinement Loop
await this.updateState({ current_task: "Refining solutions" });
let iterations = 0;
const maxIterations = 5;
while (iterations < maxIterations && this.state.progress < 0.9) {
iterations++;
this.log(`Refinement iteration ${iterations}`);
// Read feedback from blackboard
const feedbackEntries = await this.blackboard.readSection("progress");
const latestFeedback = feedbackEntries
.filter(e => e.author === "BETA")
.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())[0];
if (latestFeedback?.value?.needs_revision) {
// More work needed
await this.refineBasedOnFeedback(latestFeedback.value);
}
await this.updateState({ progress: Math.min(0.9, this.state.progress + 0.1) });
await new Promise(r => setTimeout(r, 500)); // Brief pause between iterations
}
await this.updateState({ status: "WAITING", current_task: "Awaiting consensus", progress: 0.9 });
this.log("Analysis phase complete, awaiting consensus");
}
private async analyzeProblem(task: TaskDefinition): Promise<any> {
const response = await this.callLLM(
`You are Agent ALPHA, a research and analysis specialist. Analyze the problem thoroughly.
Output JSON: {
"complexity_score": 0.0-1.0,
"key_challenges": [...],
"dependencies": [...],
"risks": [...],
"recommended_approach": "...",
"subtask_breakdown": [...]
}`,
`Objective: ${task.objective}\nConstraints: ${task.constraints.join(", ")}\nSubtasks: ${task.subtasks.map(s => s.description).join(", ")}`
);
try {
const match = response.match(/\{[\s\S]*\}/);
return match ? JSON.parse(match[0]) : { complexity_score: 0.5, key_challenges: [response] };
} catch {
return { complexity_score: 0.5, key_challenges: [response] };
}
}
private async generateProposals(task: TaskDefinition, analysis: any): Promise<any[]> {
const response = await this.callLLM(
`You are Agent ALPHA. Generate 2-3 distinct solution proposals based on the analysis.
Output JSON array: [
{ "name": "...", "approach": "...", "steps": [...], "pros": [...], "cons": [...], "confidence": 0.0-1.0 },
...
]`,
`Task: ${task.objective}\nAnalysis: ${JSON.stringify(analysis)}\nConstraints: ${task.constraints.join(", ")}`
);
try {
const match = response.match(/\[[\s\S]*\]/);
return match ? JSON.parse(match[0]) : [{ name: "Default", approach: response, steps: [], confidence: 0.5 }];
} catch {
return [{ name: "Default", approach: response, steps: [], confidence: 0.5 }];
}
}
private async refineBasedOnFeedback(feedback: any): Promise<void> {
const { proposal_id, issues, suggestions } = feedback;
if (proposal_id && this.proposals.has(proposal_id)) {
await this.reviseProposal(proposal_id, this.proposals.get(proposal_id), suggestions || []);
}
}
}
// =============================================================================
// Agent BETA - Implementation & Synthesis Specialist
// =============================================================================
export class AgentBeta extends BaseAgent {
private evaluatedProposals: Map<string, any> = new Map();
constructor(
taskId: string,
blackboard: Blackboard,
messageBus: MessageBus,
stateManager: AgentStateManager,
metrics: MetricsCollector,
model?: string
) {
super("BETA", taskId, blackboard, messageBus, stateManager, metrics, model);
}
protected async handleMessage(msg: AgentMessage): Promise<void> {
await super.handleMessage(msg);
switch (msg.type) {
case "PROPOSAL":
await this.evaluateProposal(msg);
break;
case "QUERY":
await this.handleQuery(msg);
break;
case "SYNC":
await this.handleSync(msg);
break;
}
}
private async evaluateProposal(msg: AgentMessage): Promise<void> {
const { proposal_id, proposal, phase, is_revision } = msg.payload;
this.log(`Evaluating proposal ${proposal_id} (${is_revision ? "revision" : phase})`);
await this.updateState({ current_task: `Evaluating proposal ${proposal_id}` });
const evaluation = await this.performEvaluation(proposal);
this.evaluatedProposals.set(proposal_id, { proposal, evaluation });
// Write evaluation to blackboard
await this.writeToBlackboard("progress", `eval_${proposal_id}`, {
proposal_id,
evaluation,
evaluator: this.role,
timestamp: now(),
});
// Send feedback to ALPHA
const accepted = evaluation.score >= 0.7 && evaluation.feasibility >= 0.6;
await this.sendMessage("ALPHA", "FEEDBACK", {
proposal_id,
accepted,
score: evaluation.score,
reasoning: evaluation.reasoning,
suggestions: evaluation.improvements,
needs_revision: !accepted,
}, msg.id);
if (accepted) {
this.log(`Proposal ${proposal_id} accepted with score ${evaluation.score}`);
// Cast vote for consensus
const vote: ConsensusVote = {
agent: this.role,
proposal_id,
vote: "ACCEPT",
reasoning: evaluation.reasoning,
timestamp: now(),
};
await this.blackboard.recordVote(vote);
} else {
this.log(`Proposal ${proposal_id} needs revision. Score: ${evaluation.score}`);
await this.metrics.increment("conflicts_detected");
this.state.conflicts_detected++;
}
}
private async handleQuery(msg: AgentMessage): Promise<void> {
const { question, context } = msg.payload;
this.log(`Answering query from ${msg.from}`);
const answer = await this.synthesizeAnswer(question, context);
await this.sendMessage(msg.from as AgentRole, "RESPONSE", {
question,
answer,
}, msg.id);
}
private async handleSync(msg: AgentMessage): Promise<void> {
const evaluations = Array.from(this.evaluatedProposals.entries());
await this.sendMessage(msg.from as AgentRole, "RESPONSE", {
evaluations,
progress: this.state.progress,
}, msg.id);
}
private async performEvaluation(proposal: any): Promise<any> {
const response = await this.callLLM(
`You are Agent BETA, an implementation and synthesis specialist. Evaluate this proposal critically.
Output JSON: {
"score": 0.0-1.0,
"feasibility": 0.0-1.0,
"completeness": 0.0-1.0,
"reasoning": "...",
"strengths": [...],
"weaknesses": [...],
"improvements": [...],
"implementation_notes": "..."
}`,
`Proposal: ${JSON.stringify(proposal)}`
);
try {
const match = response.match(/\{[\s\S]*\}/);
return match ? JSON.parse(match[0]) : { score: 0.5, feasibility: 0.5, reasoning: response };
} catch {
return { score: 0.5, feasibility: 0.5, reasoning: response };
}
}
private async synthesizeAnswer(question: string, context: any): Promise<any> {
const response = await this.callLLM(
`You are Agent BETA. Provide a practical, implementation-focused answer.`,
`Question: ${question}\nContext: ${JSON.stringify(context)}`
);
return { answer: response, source: this.role };
}
async run(task: TaskDefinition): Promise<void> {
await this.updateState({ status: "WORKING", current_task: "Preparing for evaluation" });
this.log(`Starting evaluation mode for: ${task.objective}`);
// Phase 1: Read and understand the problem from blackboard
await new Promise(r => setTimeout(r, 1000)); // Wait for ALPHA to write problem analysis
const problemAnalysis = await this.readFromBlackboard("problem", "analysis");
if (problemAnalysis) {
this.log(`Read problem analysis: complexity ${problemAnalysis.complexity_score}`);
}
await this.updateState({ progress: 0.1 });
// Phase 2: Active evaluation loop - wait for proposals and evaluate
await this.updateState({ current_task: "Evaluating proposals" });
let iterations = 0;
const maxIterations = 10;
while (iterations < maxIterations && this.state.progress < 0.9) {
iterations++;
// Check for new proposals on blackboard
const solutions = await this.blackboard.readSection("solutions");
const newProposals = solutions.filter(s =>
!this.evaluatedProposals.has(s.key) && s.author === "ALPHA"
);
for (const entry of newProposals) {
// Evaluate via message handling (simulated direct read)
if (!this.evaluatedProposals.has(entry.key)) {
await this.evaluateProposal({
id: generateId(),
from: "ALPHA",
to: "BETA",
type: "PROPOSAL",
payload: {
proposal_id: entry.key,
proposal: entry.value,
phase: "blackboard_read",
},
timestamp: now(),
});
}
}
await this.updateState({ progress: Math.min(0.9, 0.1 + (iterations * 0.08)) });
await new Promise(r => setTimeout(r, 500));
}
// Phase 3: Generate synthesis of best solutions
await this.updateState({ current_task: "Synthesizing final solution" });
const bestProposals = Array.from(this.evaluatedProposals.entries())
.filter(([_, v]) => v.evaluation.score >= 0.6)
.sort((a, b) => b[1].evaluation.score - a[1].evaluation.score);
if (bestProposals.length > 0) {
const synthesis = await this.synthesizeSolution(bestProposals.map(([_, v]) => v));
await this.writeToBlackboard("solutions", "synthesis", synthesis);
this.log(`Generated synthesis from ${bestProposals.length} proposals`);
// Vote for synthesis
const vote: ConsensusVote = {
agent: this.role,
proposal_id: "synthesis",
vote: "ACCEPT",
reasoning: "Synthesized best elements from top proposals",
timestamp: now(),
};
await this.blackboard.recordVote(vote);
}
await this.updateState({ status: "WAITING", current_task: "Awaiting consensus", progress: 0.9 });
this.log("Evaluation phase complete, awaiting consensus");
}
private async synthesizeSolution(proposals: any[]): Promise<any> {
const response = await this.callLLM(
`You are Agent BETA. Synthesize the best elements from these evaluated proposals into a final solution.
Output JSON: {
"final_solution": { "name": "...", "approach": "...", "steps": [...] },
"confidence": 0.0-1.0,
"sources": [...],
"trade_offs": [...]
}`,
`Proposals and evaluations: ${JSON.stringify(proposals)}`
);
try {
const match = response.match(/\{[\s\S]*\}/);
return match ? JSON.parse(match[0]) : { final_solution: { approach: response }, confidence: 0.5 };
} catch {
return { final_solution: { approach: response }, confidence: 0.5 };
}
}
}
// =============================================================================
// Agent GAMMA - Mediator & Resolver (Conditionally Spawned)
// =============================================================================
export class AgentGamma extends BaseAgent {
private spawnReason: string;
constructor(
taskId: string,
blackboard: Blackboard,
messageBus: MessageBus,
stateManager: AgentStateManager,
metrics: MetricsCollector,
spawnReason: string,
model?: string
) {
super("GAMMA", taskId, blackboard, messageBus, stateManager, metrics, model);
this.spawnReason = spawnReason;
}
protected async handleMessage(msg: AgentMessage): Promise<void> {
await super.handleMessage(msg);
switch (msg.type) {
case "QUERY":
await this.handleQuery(msg);
break;
case "HANDOFF":
await this.handleHandoff(msg);
break;
}
}
private async handleQuery(msg: AgentMessage): Promise<void> {
const { question, context } = msg.payload;
const answer = await this.mediateQuestion(question, context);
await this.sendMessage(msg.from as AgentRole, "RESPONSE", answer, msg.id);
}
private async handleHandoff(msg: AgentMessage): Promise<void> {
this.log(`Received handoff from ${msg.from}: ${JSON.stringify(msg.payload).slice(0, 100)}...`);
}
private async mediateQuestion(question: string, context: any): Promise<any> {
const response = await this.callLLM(
`You are Agent GAMMA, a mediator and conflict resolver. Provide balanced, integrative answers.`,
`Question: ${question}\nContext: ${JSON.stringify(context)}`
);
return { answer: response, mediator: true };
}
async run(task: TaskDefinition): Promise<void> {
await this.updateState({ status: "WORKING", current_task: `Resolving: ${this.spawnReason}` });
this.log(`GAMMA spawned for: ${this.spawnReason}`);
// Announce presence
await this.sendMessage("ALL", "SYNC", {
event: "GAMMA_SPAWNED",
reason: this.spawnReason,
timestamp: now(),
});
// Phase 1: Gather current state from all sources
const problemState = await this.blackboard.readSection("problem");
const solutionsState = await this.blackboard.readSection("solutions");
const progressState = await this.blackboard.readSection("progress");
const conflictsState = await this.blackboard.readSection("conflicts");
this.log(`Gathered state: ${problemState.length} problem entries, ${solutionsState.length} solutions`);
await this.updateState({ progress: 0.2 });
// Phase 2: Analyze situation based on spawn reason
let resolution: any;
switch (this.spawnReason) {
case "STUCK":
resolution = await this.resolveStuck(task, problemState, solutionsState, progressState);
break;
case "CONFLICT":
resolution = await this.resolveConflict(conflictsState, solutionsState);
break;
case "COMPLEXITY":
resolution = await this.handleComplexity(task, problemState, solutionsState);
break;
case "SUCCESS":
resolution = await this.validateSuccess(task, solutionsState);
break;
default:
resolution = await this.generalMediation(task, problemState, solutionsState);
}
await this.writeToBlackboard("progress", "gamma_resolution", resolution);
await this.updateState({ progress: 0.7 });
// Phase 3: Drive to consensus
await this.updateState({ current_task: "Driving consensus" });
const consensusResult = await this.driveConsensus(resolution);
await this.writeToBlackboard("consensus", "final", consensusResult);
// Cast final vote
const vote: ConsensusVote = {
agent: this.role,
proposal_id: "final_consensus",
vote: consensusResult.achieved ? "ACCEPT" : "ABSTAIN",
reasoning: consensusResult.reasoning,
timestamp: now(),
};
await this.blackboard.recordVote(vote);
await this.updateState({ status: "COMPLETED", progress: 1.0, current_task: "Resolution complete" });
this.log(`GAMMA resolution complete. Consensus: ${consensusResult.achieved}`);
}
private async resolveStuck(task: TaskDefinition, problem: BlackboardEntry[], solutions: BlackboardEntry[], progress: BlackboardEntry[]): Promise<any> {
this.log("Analyzing stuck condition...");
const response = await this.callLLM(
`You are Agent GAMMA, a mediator. The other agents appear stuck. Analyze the situation and provide direction.
Output JSON: {
"diagnosis": "why agents are stuck",
"blockers": [...],
"recommended_actions": [...],
"new_approach": "...",
"confidence": 0.0-1.0
}`,
`Task: ${task.objective}\nProblem analysis: ${JSON.stringify(problem)}\nSolutions so far: ${JSON.stringify(solutions)}\nProgress: ${JSON.stringify(progress)}`
);
try {
const match = response.match(/\{[\s\S]*\}/);
const result = match ? JSON.parse(match[0]) : { diagnosis: response, confidence: 0.5 };
// Broadcast new direction to other agents
await this.sendMessage("ALL", "HANDOFF", {
type: "NEW_DIRECTION",
...result,
});
return result;
} catch {
return { diagnosis: response, confidence: 0.5 };
}
}
private async resolveConflict(conflicts: BlackboardEntry[], solutions: BlackboardEntry[]): Promise<any> {
this.log("Mediating conflicts...");
const response = await this.callLLM(
`You are Agent GAMMA, a conflict mediator. Resolve the conflicts between proposals.
Output JSON: {
"conflicts_analyzed": [...],
"resolution": "...",
"compromise_solution": {...},
"reasoning": "...",
"confidence": 0.0-1.0
}`,
`Conflicts: ${JSON.stringify(conflicts)}\nSolutions: ${JSON.stringify(solutions)}`
);
try {
const match = response.match(/\{[\s\S]*\}/);
const result = match ? JSON.parse(match[0]) : { resolution: response, confidence: 0.5 };
await this.metrics.increment("conflicts_resolved");
return result;
} catch {
return { resolution: response, confidence: 0.5 };
}
}
private async handleComplexity(task: TaskDefinition, problem: BlackboardEntry[], solutions: BlackboardEntry[]): Promise<any> {
this.log("Breaking down complexity...");
const response = await this.callLLM(
`You are Agent GAMMA. The task is too complex. Break it into manageable pieces.
Output JSON: {
"complexity_analysis": "...",
"decomposition": [...subtasks...],
"priority_order": [...],
"delegation": { "ALPHA": [...], "BETA": [...] },
"confidence": 0.0-1.0
}`,
`Task: ${task.objective}\nProblem: ${JSON.stringify(problem)}\nCurrent solutions: ${JSON.stringify(solutions)}`
);
try {
const match = response.match(/\{[\s\S]*\}/);
const result = match ? JSON.parse(match[0]) : { decomposition: [response], confidence: 0.5 };
// Delegate subtasks
if (result.delegation) {
if (result.delegation.ALPHA) {
await this.sendMessage("ALPHA", "HANDOFF", {
type: "SUBTASK_ASSIGNMENT",
tasks: result.delegation.ALPHA,
});
}
if (result.delegation.BETA) {
await this.sendMessage("BETA", "HANDOFF", {
type: "SUBTASK_ASSIGNMENT",
tasks: result.delegation.BETA,
});
}
}
return result;
} catch {
return { decomposition: [response], confidence: 0.5 };
}
}
private async validateSuccess(task: TaskDefinition, solutions: BlackboardEntry[]): Promise<any> {
this.log("Validating task success...");
const response = await this.callLLM(
`You are Agent GAMMA. Validate that the task has been successfully completed.
Output JSON: {
"success": true/false,
"criteria_met": [...],
"criteria_unmet": [...],
"final_assessment": "...",
"recommendations": [...],
"confidence": 0.0-1.0
}`,
`Task: ${task.objective}\nSuccess criteria: ${task.success_criteria.join(", ")}\nSolutions: ${JSON.stringify(solutions)}`
);
try {
const match = response.match(/\{[\s\S]*\}/);
return match ? JSON.parse(match[0]) : { success: false, final_assessment: response };
} catch {
return { success: false, final_assessment: response };
}
}
private async generalMediation(task: TaskDefinition, problem: BlackboardEntry[], solutions: BlackboardEntry[]): Promise<any> {
this.log("General mediation...");
const response = await this.callLLM(
`You are Agent GAMMA, a general mediator. Help coordinate the other agents toward a solution.
Output JSON: {
"assessment": "...",
"recommendations": [...],
"next_steps": [...],
"confidence": 0.0-1.0
}`,
`Task: ${task.objective}\nProblem: ${JSON.stringify(problem)}\nSolutions: ${JSON.stringify(solutions)}`
);
try {
const match = response.match(/\{[\s\S]*\}/);
return match ? JSON.parse(match[0]) : { assessment: response, confidence: 0.5 };
} catch {
return { assessment: response, confidence: 0.5 };
}
}
private async driveConsensus(resolution: any): Promise<any> {
this.log("Driving to consensus...");
// Check existing votes
const synthesisVotes = await this.blackboard.getVotes("synthesis");
const acceptVotes = synthesisVotes.filter(v => v.vote === "ACCEPT");
const response = await this.callLLM(
`You are Agent GAMMA. Based on the resolution and votes, determine if consensus is achieved.
Output JSON: {
"achieved": true/false,
"reasoning": "...",
"final_decision": "...",
"dissenting_views": [...],
"action_items": [...]
}`,
`Resolution: ${JSON.stringify(resolution)}\nVotes: ${JSON.stringify(synthesisVotes)}\nAccept count: ${acceptVotes.length}`
);
try {
const match = response.match(/\{[\s\S]*\}/);
return match ? JSON.parse(match[0]) : { achieved: acceptVotes.length >= 2, reasoning: response };
} catch {
return { achieved: acceptVotes.length >= 2, reasoning: response };
}
}
}
export { BaseAgent };

108
agents/multi-agent/bun.lock Normal file
View File

@ -0,0 +1,108 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "multi-agent-coordination",
"dependencies": {
"openai": "^4.0.0",
"redis": "^4.6.0",
},
},
},
"packages": {
"@redis/bloom": ["@redis/bloom@1.2.0", "", { "peerDependencies": { "@redis/client": "^1.0.0" } }, "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg=="],
"@redis/client": ["@redis/client@1.6.1", "", { "dependencies": { "cluster-key-slot": "1.1.2", "generic-pool": "3.9.0", "yallist": "4.0.0" } }, "sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw=="],
"@redis/graph": ["@redis/graph@1.1.1", "", { "peerDependencies": { "@redis/client": "^1.0.0" } }, "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw=="],
"@redis/json": ["@redis/json@1.0.7", "", { "peerDependencies": { "@redis/client": "^1.0.0" } }, "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ=="],
"@redis/search": ["@redis/search@1.2.0", "", { "peerDependencies": { "@redis/client": "^1.0.0" } }, "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw=="],
"@redis/time-series": ["@redis/time-series@1.1.0", "", { "peerDependencies": { "@redis/client": "^1.0.0" } }, "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g=="],
"@types/node": ["@types/node@18.19.130", "", { "dependencies": { "undici-types": "~5.26.4" } }, "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg=="],
"@types/node-fetch": ["@types/node-fetch@2.6.13", "", { "dependencies": { "@types/node": "*", "form-data": "^4.0.4" } }, "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw=="],
"abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
"agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="],
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
"cluster-key-slot": ["cluster-key-slot@1.1.2", "", {}, "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA=="],
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
"event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
"form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
"form-data-encoder": ["form-data-encoder@1.7.2", "", {}, "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A=="],
"formdata-node": ["formdata-node@4.4.1", "", { "dependencies": { "node-domexception": "1.0.0", "web-streams-polyfill": "4.0.0-beta.3" } }, "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ=="],
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"generic-pool": ["generic-pool@3.9.0", "", {}, "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g=="],
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
"humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="],
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="],
"node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
"openai": ["openai@4.104.0", "", { "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", "formdata-node": "^4.3.2", "node-fetch": "^2.6.7" }, "peerDependencies": { "ws": "^8.18.0", "zod": "^3.23.8" }, "optionalPeers": ["ws", "zod"], "bin": { "openai": "bin/cli" } }, "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA=="],
"redis": ["redis@4.7.1", "", { "dependencies": { "@redis/bloom": "1.2.0", "@redis/client": "1.6.1", "@redis/graph": "1.1.1", "@redis/json": "1.0.7", "@redis/search": "1.2.0", "@redis/time-series": "1.1.0" } }, "sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ=="],
"tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
"undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="],
"web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="],
"webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
"whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
"yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="],
}
}

View File

@ -0,0 +1,500 @@
/**
* Multi-Agent Coordination System - Blackboard & Messaging
*/
import { createClient, RedisClientType } from "redis";
import { $ } from "bun";
import type {
AgentRole,
AgentMessage,
BlackboardEntry,
BlackboardSection,
AgentState,
CoordinationMetrics,
SpawnCondition,
ConsensusVote,
} from "./types";
function now(): string {
return new Date().toISOString();
}
function generateId(): string {
return Math.random().toString(36).slice(2, 10) + Date.now().toString(36);
}
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;
}
// =============================================================================
// Blackboard System - Shared Memory for Agent Coordination
// =============================================================================
export class Blackboard {
private redis!: RedisClientType;
private taskId: string;
private versionCounters: Map<string, number> = new Map();
constructor(taskId: string) {
this.taskId = taskId;
}
async connect(): Promise<void> {
const creds = await getVaultSecret("services/dragonfly");
this.redis = createClient({
url: "redis://" + creds.host + ":" + creds.port,
password: creds.password,
});
await this.redis.connect();
}
async disconnect(): Promise<void> {
await this.redis.quit();
}
private key(section: BlackboardSection, entryKey: string): string {
return `blackboard:${this.taskId}:${section}:${entryKey}`;
}
private sectionKey(section: BlackboardSection): string {
return `blackboard:${this.taskId}:${section}`;
}
async write(section: BlackboardSection, entryKey: string, value: any, author: AgentRole): Promise<BlackboardEntry> {
const fullKey = this.key(section, entryKey);
const currentVersion = this.versionCounters.get(fullKey) || 0;
const newVersion = currentVersion + 1;
this.versionCounters.set(fullKey, newVersion);
const entry: BlackboardEntry = {
section,
key: entryKey,
value,
author,
version: newVersion,
timestamp: now(),
};
await this.redis.hSet(this.sectionKey(section), entryKey, JSON.stringify(entry));
await this.redis.rPush(`blackboard:${this.taskId}:history`, JSON.stringify({
action: "WRITE",
...entry,
}));
// Increment metrics
await this.redis.hIncrBy(`metrics:${this.taskId}`, "blackboard_writes", 1);
return entry;
}
async read(section: BlackboardSection, entryKey: string): Promise<BlackboardEntry | null> {
const data = await this.redis.hGet(this.sectionKey(section), entryKey);
if (!data) return null;
await this.redis.hIncrBy(`metrics:${this.taskId}`, "blackboard_reads", 1);
return JSON.parse(data);
}
async readSection(section: BlackboardSection): Promise<BlackboardEntry[]> {
const data = await this.redis.hGetAll(this.sectionKey(section));
const entries: BlackboardEntry[] = [];
for (const value of Object.values(data)) {
entries.push(JSON.parse(value));
}
await this.redis.hIncrBy(`metrics:${this.taskId}`, "blackboard_reads", entries.length);
return entries;
}
async getHistory(limit: number = 100): Promise<any[]> {
const data = await this.redis.lRange(`blackboard:${this.taskId}:history`, -limit, -1);
return data.map(d => JSON.parse(d));
}
// Consensus tracking
async recordVote(vote: ConsensusVote): Promise<void> {
await this.redis.rPush(`blackboard:${this.taskId}:votes:${vote.proposal_id}`, JSON.stringify(vote));
await this.write("consensus", vote.proposal_id + ":" + vote.agent, vote, vote.agent);
}
async getVotes(proposalId: string): Promise<ConsensusVote[]> {
const data = await this.redis.lRange(`blackboard:${this.taskId}:votes:${proposalId}`, 0, -1);
return data.map(d => JSON.parse(d));
}
async checkConsensus(proposalId: string, requiredAgents: AgentRole[]): Promise<{ reached: boolean; votes: ConsensusVote[] }> {
const votes = await this.getVotes(proposalId);
const acceptVotes = votes.filter(v => v.vote === "ACCEPT");
const rejectVotes = votes.filter(v => v.vote === "REJECT");
// Consensus requires majority accept and no rejects from required agents
const hasAllRequired = requiredAgents.every(agent =>
votes.some(v => v.agent === agent)
);
const reached = hasAllRequired &&
acceptVotes.length > rejectVotes.length &&
rejectVotes.length === 0;
return { reached, votes };
}
}
// =============================================================================
// Direct Messaging System - Point-to-Point Communication
// =============================================================================
export class MessageBus {
private redis!: RedisClientType;
private subscriber!: RedisClientType;
private taskId: string;
private agentRole: AgentRole;
private messageHandlers: Map<string, (msg: AgentMessage) => void> = new Map();
constructor(taskId: string, agentRole: AgentRole) {
this.taskId = taskId;
this.agentRole = agentRole;
}
async connect(): Promise<void> {
const creds = await getVaultSecret("services/dragonfly");
const config = {
url: "redis://" + creds.host + ":" + creds.port,
password: creds.password,
};
this.redis = createClient(config);
this.subscriber = createClient(config);
await this.redis.connect();
await this.subscriber.connect();
// Subscribe to direct messages and broadcast
await this.subscriber.subscribe(`msg:${this.taskId}:${this.agentRole}`, (message) => {
this.handleMessage(JSON.parse(message));
});
await this.subscriber.subscribe(`msg:${this.taskId}:ALL`, (message) => {
this.handleMessage(JSON.parse(message));
});
}
async disconnect(): Promise<void> {
await this.subscriber.unsubscribe();
await this.subscriber.quit();
await this.redis.quit();
}
private handleMessage(msg: AgentMessage): void {
// Store in message log
this.redis.rPush(`msg:${this.taskId}:log`, JSON.stringify(msg));
this.redis.hIncrBy(`metrics:${this.taskId}`, "total_messages", 1);
this.redis.hIncrBy(`metrics:${this.taskId}`, "direct_messages", 1);
// Call registered handlers
for (const handler of this.messageHandlers.values()) {
try {
handler(msg);
} catch (e) {
console.error("Message handler error:", e);
}
}
}
onMessage(handlerId: string, handler: (msg: AgentMessage) => void): void {
this.messageHandlers.set(handlerId, handler);
}
removeHandler(handlerId: string): void {
this.messageHandlers.delete(handlerId);
}
async send(to: AgentRole | "ALL", type: AgentMessage["type"], payload: any, correlationId?: string): Promise<AgentMessage> {
const msg: AgentMessage = {
id: generateId(),
from: this.agentRole,
to,
type,
payload,
timestamp: now(),
correlation_id: correlationId,
};
await this.redis.publish(`msg:${this.taskId}:${to}`, JSON.stringify(msg));
await this.redis.rPush(`msg:${this.taskId}:log`, JSON.stringify(msg));
await this.redis.hIncrBy(`metrics:${this.taskId}`, "total_messages", 1);
await this.redis.hIncrBy(`metrics:${this.taskId}`, "direct_messages", 1);
return msg;
}
async getMessageLog(limit: number = 100): Promise<AgentMessage[]> {
const data = await this.redis.lRange(`msg:${this.taskId}:log`, -limit, -1);
return data.map(d => JSON.parse(d));
}
}
// =============================================================================
// Agent State Manager
// =============================================================================
export class AgentStateManager {
private redis!: RedisClientType;
private taskId: string;
constructor(taskId: string) {
this.taskId = taskId;
}
async connect(): Promise<void> {
const creds = await getVaultSecret("services/dragonfly");
this.redis = createClient({
url: "redis://" + creds.host + ":" + creds.port,
password: creds.password,
});
await this.redis.connect();
}
async disconnect(): Promise<void> {
await this.redis.quit();
}
async updateState(state: AgentState): Promise<void> {
state.last_activity = now();
await this.redis.hSet(`agents:${this.taskId}`, state.role, JSON.stringify(state));
}
async getState(role: AgentRole): Promise<AgentState | null> {
const data = await this.redis.hGet(`agents:${this.taskId}`, role);
return data ? JSON.parse(data) : null;
}
async getAllStates(): Promise<AgentState[]> {
const data = await this.redis.hGetAll(`agents:${this.taskId}`);
return Object.values(data).map(d => JSON.parse(d));
}
async isBlocked(role: AgentRole, thresholdSeconds: number): Promise<boolean> {
const state = await this.getState(role);
if (!state || !state.blocked_since) return false;
const blockedDuration = (Date.now() - new Date(state.blocked_since).getTime()) / 1000;
return blockedDuration > thresholdSeconds;
}
async detectStuckAgents(thresholdSeconds: number): Promise<AgentRole[]> {
const states = await this.getAllStates();
const stuckAgents: AgentRole[] = [];
for (const state of states) {
if (state.status === "COMPLETED" || state.status === "FAILED") continue;
const lastActivity = new Date(state.last_activity).getTime();
const inactivitySeconds = (Date.now() - lastActivity) / 1000;
if (inactivitySeconds > thresholdSeconds) {
stuckAgents.push(state.role);
}
}
return stuckAgents;
}
}
// =============================================================================
// Spawn Controller - Manages Conditional Agent Spawning
// =============================================================================
export class SpawnController {
private redis!: RedisClientType;
private taskId: string;
private conditions: SpawnCondition[] = [];
private gammaSpawned: boolean = false;
constructor(taskId: string) {
this.taskId = taskId;
}
async connect(): Promise<void> {
const creds = await getVaultSecret("services/dragonfly");
this.redis = createClient({
url: "redis://" + creds.host + ":" + creds.port,
password: creds.password,
});
await this.redis.connect();
// Initialize default spawn conditions
this.conditions = [
{
type: "STUCK",
threshold: 30, // seconds
current_value: 0,
triggered: false,
description: "Spawn GAMMA when agents are stuck for 30+ seconds",
},
{
type: "CONFLICT",
threshold: 3, // conflicts
current_value: 0,
triggered: false,
description: "Spawn GAMMA when 3+ unresolved conflicts detected",
},
{
type: "COMPLEXITY",
threshold: 0.8, // complexity score
current_value: 0,
triggered: false,
description: "Spawn GAMMA when task complexity exceeds 0.8",
},
{
type: "SUCCESS",
threshold: 1.0, // completion
current_value: 0,
triggered: false,
description: "Spawn GAMMA to validate and finalize when task complete",
},
];
}
async disconnect(): Promise<void> {
await this.redis.quit();
}
async updateCondition(type: SpawnCondition["type"], value: number): Promise<SpawnCondition | null> {
const condition = this.conditions.find(c => c.type === type);
if (!condition) return null;
condition.current_value = value;
condition.triggered = value >= condition.threshold;
await this.redis.hSet(`spawn:${this.taskId}:conditions`, type, JSON.stringify(condition));
return condition;
}
async checkSpawnConditions(): Promise<{ shouldSpawn: boolean; reason: SpawnCondition | null }> {
if (this.gammaSpawned) {
return { shouldSpawn: false, reason: null };
}
for (const condition of this.conditions) {
if (condition.triggered) {
return { shouldSpawn: true, reason: condition };
}
}
return { shouldSpawn: false, reason: null };
}
async markGammaSpawned(reason: SpawnCondition): Promise<void> {
this.gammaSpawned = true;
await this.redis.hSet(`metrics:${this.taskId}`, "gamma_spawned", "true");
await this.redis.hSet(`metrics:${this.taskId}`, "gamma_spawn_reason", reason.type);
await this.redis.hSet(`metrics:${this.taskId}`, "gamma_spawn_time", now());
}
isGammaSpawned(): boolean {
return this.gammaSpawned;
}
getConditions(): SpawnCondition[] {
return this.conditions;
}
}
// =============================================================================
// Metrics Collector
// =============================================================================
export class MetricsCollector {
private redis!: RedisClientType;
private taskId: string;
private startTime: number;
constructor(taskId: string) {
this.taskId = taskId;
this.startTime = Date.now();
}
async connect(): Promise<void> {
const creds = await getVaultSecret("services/dragonfly");
this.redis = createClient({
url: "redis://" + creds.host + ":" + creds.port,
password: creds.password,
});
await this.redis.connect();
await this.redis.hSet(`metrics:${this.taskId}`, {
task_id: this.taskId,
start_time: now(),
total_messages: "0",
direct_messages: "0",
blackboard_writes: "0",
blackboard_reads: "0",
conflicts_detected: "0",
conflicts_resolved: "0",
gamma_spawned: "false",
final_consensus: "false",
performance_score: "0",
});
}
async disconnect(): Promise<void> {
await this.redis.quit();
}
async increment(metric: string, by: number = 1): Promise<void> {
await this.redis.hIncrBy(`metrics:${this.taskId}`, metric, by);
}
async set(metric: string, value: string): Promise<void> {
await this.redis.hSet(`metrics:${this.taskId}`, metric, value);
}
async getMetrics(): Promise<CoordinationMetrics> {
const data = await this.redis.hGetAll(`metrics:${this.taskId}`);
return {
task_id: this.taskId,
start_time: data.start_time || now(),
end_time: data.end_time,
total_messages: parseInt(data.total_messages || "0"),
direct_messages: parseInt(data.direct_messages || "0"),
blackboard_writes: parseInt(data.blackboard_writes || "0"),
blackboard_reads: parseInt(data.blackboard_reads || "0"),
conflicts_detected: parseInt(data.conflicts_detected || "0"),
conflicts_resolved: parseInt(data.conflicts_resolved || "0"),
gamma_spawned: data.gamma_spawned === "true",
gamma_spawn_reason: data.gamma_spawn_reason,
gamma_spawn_time: data.gamma_spawn_time,
final_consensus: data.final_consensus === "true",
performance_score: parseFloat(data.performance_score || "0"),
};
}
async finalize(consensus: boolean): Promise<CoordinationMetrics> {
const endTime = now();
const elapsedMs = Date.now() - this.startTime;
await this.redis.hSet(`metrics:${this.taskId}`, "end_time", endTime);
await this.redis.hSet(`metrics:${this.taskId}`, "final_consensus", consensus ? "true" : "false");
// Calculate performance score
const metrics = await this.getMetrics();
const messageEfficiency = Math.max(0, 1 - (metrics.total_messages / 100));
const conflictPenalty = metrics.conflicts_detected * 0.1;
const timePenalty = Math.min(0.5, elapsedMs / 120000);
const consensusBonus = consensus ? 0.2 : 0;
const gammaBonus = metrics.gamma_spawned && consensus ? 0.1 : (metrics.gamma_spawned ? -0.1 : 0);
const score = Math.max(0, Math.min(1,
0.5 + messageEfficiency * 0.2 - conflictPenalty - timePenalty + consensusBonus + gammaBonus
));
await this.redis.hSet(`metrics:${this.taskId}`, "performance_score", score.toFixed(3));
return this.getMetrics();
}
}

View File

@ -0,0 +1,410 @@
/**
* Multi-Agent Coordination System - Orchestrator
* Manages parallel agent execution, spawn conditions, and metrics
*/
import type { TaskDefinition, CoordinationMetrics, SpawnCondition, AgentRole } from "./types";
import {
Blackboard,
MessageBus,
AgentStateManager,
SpawnController,
MetricsCollector,
} from "./coordination";
import { AgentAlpha, AgentBeta, AgentGamma } from "./agents";
function now(): string {
return new Date().toISOString();
}
function generateId(): string {
return "task-" + Math.random().toString(36).slice(2, 8) + "-" + Date.now().toString(36);
}
// =============================================================================
// Multi-Agent Orchestrator
// =============================================================================
export class MultiAgentOrchestrator {
private taskId: string;
private blackboard!: Blackboard;
private stateManager!: AgentStateManager;
private spawnController!: SpawnController;
private metrics!: MetricsCollector;
private alphaAgent!: AgentAlpha;
private betaAgent!: AgentBeta;
private gammaAgent?: AgentGamma;
private alphaBus!: MessageBus;
private betaBus!: MessageBus;
private gammaBus?: MessageBus;
private model: string;
private startTime!: number;
private monitorInterval?: ReturnType<typeof setInterval>;
constructor(model: string = "anthropic/claude-sonnet-4") {
this.taskId = generateId();
this.model = model;
}
private log(msg: string) {
const elapsed = this.startTime ? ((Date.now() - this.startTime) / 1000).toFixed(1) : "0.0";
console.log(`[${elapsed}s] [ORCHESTRATOR] ${msg}`);
}
async initialize(): Promise<void> {
this.startTime = Date.now();
console.log("\n" + "=".repeat(70));
console.log("MULTI-AGENT COORDINATION SYSTEM");
console.log("Task ID: " + this.taskId);
console.log("Model: " + this.model);
console.log("=".repeat(70) + "\n");
this.log("Initializing coordination infrastructure...");
// Initialize shared infrastructure
this.blackboard = new Blackboard(this.taskId);
this.stateManager = new AgentStateManager(this.taskId);
this.spawnController = new SpawnController(this.taskId);
this.metrics = new MetricsCollector(this.taskId);
await Promise.all([
this.blackboard.connect(),
this.stateManager.connect(),
this.spawnController.connect(),
this.metrics.connect(),
]);
this.log("Infrastructure connected");
// Initialize message buses for ALPHA and BETA
this.alphaBus = new MessageBus(this.taskId, "ALPHA");
this.betaBus = new MessageBus(this.taskId, "BETA");
await Promise.all([
this.alphaBus.connect(),
this.betaBus.connect(),
]);
this.log("Message buses connected");
// Create initial agents
this.alphaAgent = new AgentAlpha(
this.taskId, this.blackboard, this.alphaBus, this.stateManager, this.metrics, this.model
);
this.betaAgent = new AgentBeta(
this.taskId, this.blackboard, this.betaBus, this.stateManager, this.metrics, this.model
);
await Promise.all([
this.alphaAgent.init(),
this.betaAgent.init(),
]);
this.log("Agents ALPHA and BETA initialized");
}
async spawnGamma(reason: SpawnCondition): Promise<void> {
if (this.gammaAgent) {
this.log("GAMMA already spawned, skipping");
return;
}
this.log(`SPAWNING GAMMA - Reason: ${reason.type} (threshold: ${reason.threshold}, current: ${reason.current_value})`);
// Create message bus for GAMMA
this.gammaBus = new MessageBus(this.taskId, "GAMMA");
await this.gammaBus.connect();
// Create and initialize GAMMA agent
this.gammaAgent = new AgentGamma(
this.taskId, this.blackboard, this.gammaBus, this.stateManager, this.metrics,
reason.type, this.model
);
await this.gammaAgent.init();
await this.spawnController.markGammaSpawned(reason);
this.log("GAMMA agent spawned and initialized");
}
private async monitorConditions(): Promise<void> {
// Check stuck condition
const stuckAgents = await this.stateManager.detectStuckAgents(30);
if (stuckAgents.length > 0) {
this.log(`Stuck agents detected: ${stuckAgents.join(", ")}`);
const condition = await this.spawnController.updateCondition("STUCK", stuckAgents.length);
if (condition?.triggered) {
const { shouldSpawn, reason } = await this.spawnController.checkSpawnConditions();
if (shouldSpawn && reason) {
await this.spawnGamma(reason);
}
}
}
// Check conflict condition
const metricsData = await this.metrics.getMetrics();
const unresolvedConflicts = metricsData.conflicts_detected - metricsData.conflicts_resolved;
const conflictCondition = await this.spawnController.updateCondition("CONFLICT", unresolvedConflicts);
if (conflictCondition?.triggered && !this.spawnController.isGammaSpawned()) {
const { shouldSpawn, reason } = await this.spawnController.checkSpawnConditions();
if (shouldSpawn && reason) {
await this.spawnGamma(reason);
}
}
// Check complexity condition (from blackboard)
const analysis = await this.blackboard.read("problem", "analysis");
if (analysis?.value?.complexity_score) {
const complexityCondition = await this.spawnController.updateCondition("COMPLEXITY", analysis.value.complexity_score);
if (complexityCondition?.triggered && !this.spawnController.isGammaSpawned()) {
const { shouldSpawn, reason } = await this.spawnController.checkSpawnConditions();
if (shouldSpawn && reason) {
await this.spawnGamma(reason);
}
}
}
// Log current state
const states = await this.stateManager.getAllStates();
const statesSummary = states.map(s => `${s.role}:${s.status}(${(s.progress * 100).toFixed(0)}%)`).join(", ");
this.log(`Status: ${statesSummary} | Messages: ${metricsData.total_messages} | Conflicts: ${unresolvedConflicts}`);
}
async runTask(task: TaskDefinition): Promise<CoordinationMetrics> {
this.log(`Starting task: ${task.objective.slice(0, 60)}...`);
// Write task to blackboard
await this.blackboard.write("problem", "task_definition", task, "ALPHA");
// Start monitoring
this.monitorInterval = setInterval(() => this.monitorConditions(), 2000);
// Run agents in parallel
this.log("Launching ALPHA and BETA in parallel...");
const alphaPromise = this.alphaAgent.run(task).catch(e => {
this.log(`ALPHA error: ${e.message}`);
});
const betaPromise = this.betaAgent.run(task).catch(e => {
this.log(`BETA error: ${e.message}`);
});
// Wait for initial agents to complete (or timeout)
const timeout = task.timeout_seconds * 1000;
const timeoutPromise = new Promise<void>(resolve => setTimeout(resolve, timeout));
await Promise.race([
Promise.all([alphaPromise, betaPromise]),
timeoutPromise,
]);
this.log("Initial agents completed or timeout reached");
// Check if GAMMA needs to be spawned for success validation
const states = await this.stateManager.getAllStates();
const bothComplete = states.every(s => s.status === "WAITING" || s.status === "COMPLETED");
if (bothComplete && !this.spawnController.isGammaSpawned()) {
await this.spawnController.updateCondition("SUCCESS", 1.0);
const { shouldSpawn, reason } = await this.spawnController.checkSpawnConditions();
if (shouldSpawn && reason) {
await this.spawnGamma(reason);
}
}
// If GAMMA was spawned, run it
if (this.gammaAgent) {
this.log("Running GAMMA for resolution...");
await this.gammaAgent.run(task).catch(e => {
this.log(`GAMMA error: ${e.message}`);
});
}
// Stop monitoring
if (this.monitorInterval) {
clearInterval(this.monitorInterval);
}
// Check consensus
const consensus = await this.blackboard.checkConsensus("synthesis", ["ALPHA", "BETA"]);
const consensusAchieved = consensus.reached ||
(await this.blackboard.read("consensus", "final"))?.value?.achieved === true;
this.log(`Consensus achieved: ${consensusAchieved}`);
// Finalize metrics
const finalMetrics = await this.metrics.finalize(consensusAchieved);
return finalMetrics;
}
async cleanup(): Promise<void> {
this.log("Cleaning up...");
if (this.monitorInterval) {
clearInterval(this.monitorInterval);
}
await Promise.all([
this.alphaBus?.disconnect(),
this.betaBus?.disconnect(),
this.gammaBus?.disconnect(),
this.blackboard?.disconnect(),
this.stateManager?.disconnect(),
this.spawnController?.disconnect(),
this.metrics?.disconnect(),
].filter(Boolean));
this.log("Cleanup complete");
}
getTaskId(): string {
return this.taskId;
}
}
// =============================================================================
// Performance Analysis
// =============================================================================
export function analyzePerformance(metrics: CoordinationMetrics): void {
console.log("\n" + "=".repeat(70));
console.log("PERFORMANCE ANALYSIS");
console.log("=".repeat(70));
const duration = metrics.end_time
? (new Date(metrics.end_time).getTime() - new Date(metrics.start_time).getTime()) / 1000
: 0;
console.log("\nTiming:");
console.log(` Duration: ${duration.toFixed(1)}s`);
console.log("\nCommunication:");
console.log(` Total messages: ${metrics.total_messages}`);
console.log(` Direct messages: ${metrics.direct_messages}`);
console.log(` Blackboard writes: ${metrics.blackboard_writes}`);
console.log(` Blackboard reads: ${metrics.blackboard_reads}`);
console.log(` Communication overhead: ${((metrics.total_messages + metrics.blackboard_writes) / duration).toFixed(2)} ops/sec`);
console.log("\nCoordination:");
console.log(` Conflicts detected: ${metrics.conflicts_detected}`);
console.log(` Conflicts resolved: ${metrics.conflicts_resolved}`);
console.log(` Conflict resolution rate: ${metrics.conflicts_detected > 0 ? ((metrics.conflicts_resolved / metrics.conflicts_detected) * 100).toFixed(1) : 100}%`);
console.log("\nGamma Agent:");
console.log(` Spawned: ${metrics.gamma_spawned ? "Yes" : "No"}`);
if (metrics.gamma_spawned) {
console.log(` Spawn reason: ${metrics.gamma_spawn_reason}`);
console.log(` Spawn time: ${metrics.gamma_spawn_time}`);
}
console.log("\nOutcome:");
console.log(` Consensus achieved: ${metrics.final_consensus ? "Yes" : "No"}`);
console.log(` Performance score: ${(metrics.performance_score * 100).toFixed(1)}%`);
// Threshold analysis
console.log("\nThreshold Effects:");
const messageThreshold = 50;
const conflictThreshold = 3;
if (metrics.total_messages > messageThreshold) {
console.log(` ! High message volume (${metrics.total_messages} > ${messageThreshold}) - potential coordination overhead`);
} else {
console.log(` + Message volume within threshold (${metrics.total_messages} <= ${messageThreshold})`);
}
if (metrics.conflicts_detected > conflictThreshold) {
console.log(` ! High conflict rate (${metrics.conflicts_detected} > ${conflictThreshold}) - agents may have divergent strategies`);
} else {
console.log(` + Conflict rate within threshold (${metrics.conflicts_detected} <= ${conflictThreshold})`);
}
if (metrics.gamma_spawned && metrics.gamma_spawn_reason === "STUCK") {
console.log(` ! Gamma spawned due to stuck condition - consider adjusting agent strategies`);
}
console.log("\n" + "=".repeat(70));
}
// =============================================================================
// CLI Entry Point
// =============================================================================
async function main() {
const args = process.argv.slice(2);
// Default complex task
let objective = args[0] || `Design a distributed event-driven architecture for a real-time analytics platform that handles:
1) High-throughput data ingestion from multiple sources
2) Stream processing with exactly-once semantics
3) Real-time aggregations and windowed computations
4) Low-latency query serving for dashboards
5) Horizontal scalability to handle 1M events/second
The solution should consider fault tolerance, data consistency, and cost optimization.`;
let model = "anthropic/claude-sonnet-4";
const modelIdx = args.indexOf("--model");
if (modelIdx !== -1 && args[modelIdx + 1]) {
model = args[modelIdx + 1];
}
// Parse timeout
let timeout = 120;
const timeoutIdx = args.indexOf("--timeout");
if (timeoutIdx !== -1 && args[timeoutIdx + 1]) {
timeout = parseInt(args[timeoutIdx + 1]);
}
const task: TaskDefinition = {
task_id: generateId(),
objective,
complexity: "high",
subtasks: [
{ id: "s1", description: "Analyze data ingestion requirements", status: "pending", dependencies: [] },
{ id: "s2", description: "Design stream processing pipeline", status: "pending", dependencies: ["s1"] },
{ id: "s3", description: "Plan storage and query layer", status: "pending", dependencies: ["s1"] },
{ id: "s4", description: "Define scalability strategy", status: "pending", dependencies: ["s2", "s3"] },
{ id: "s5", description: "Integrate fault tolerance mechanisms", status: "pending", dependencies: ["s4"] },
],
constraints: [
"Must use open-source technologies where possible",
"Latency < 100ms for query responses",
"Support for multiple data formats (JSON, Avro, Protobuf)",
"Cost-effective for variable workloads",
],
success_criteria: [
"Complete architecture design with component diagrams",
"Data flow specifications",
"Scalability analysis",
"Fault tolerance mechanisms documented",
"Cost estimation provided",
],
timeout_seconds: timeout,
};
const orchestrator = new MultiAgentOrchestrator(model);
try {
await orchestrator.initialize();
const metrics = await orchestrator.runTask(task);
console.log("\n" + "=".repeat(70));
console.log("FINAL METRICS");
console.log("=".repeat(70));
console.log(JSON.stringify(metrics, null, 2));
analyzePerformance(metrics);
} catch (e: any) {
console.error("Orchestrator error:", e.message);
} finally {
await orchestrator.cleanup();
}
}
main().catch(console.error);

View File

@ -0,0 +1,14 @@
{
"name": "multi-agent-coordination",
"version": "0.1.0",
"description": "Multi-agent coordination system with parallel execution, shared blackboard, and conditional spawning",
"main": "orchestrator.ts",
"scripts": {
"start": "bun run orchestrator.ts",
"test": "bun run orchestrator.ts --timeout 60"
},
"dependencies": {
"openai": "^4.0.0",
"redis": "^4.6.0"
}
}

View File

@ -0,0 +1,92 @@
/**
* Multi-Agent Coordination System - Type Definitions
*/
export type AgentRole = "ALPHA" | "BETA" | "GAMMA";
export type AgentStatus = "IDLE" | "WORKING" | "WAITING" | "BLOCKED" | "COMPLETED" | "FAILED";
export type MessageType = "PROPOSAL" | "FEEDBACK" | "QUERY" | "RESPONSE" | "SYNC" | "HANDOFF" | "SPAWN_REQUEST";
export type BlackboardSection = "problem" | "solutions" | "constraints" | "progress" | "conflicts" | "consensus";
export interface AgentMessage {
id: string;
from: AgentRole;
to: AgentRole | "ALL" | "BLACKBOARD";
type: MessageType;
payload: any;
timestamp: string;
correlation_id?: string;
}
export interface BlackboardEntry {
section: BlackboardSection;
key: string;
value: any;
author: AgentRole;
version: number;
timestamp: string;
supersedes?: string;
}
export interface AgentState {
agent_id: string;
role: AgentRole;
status: AgentStatus;
current_task: string;
progress: number;
confidence: number;
last_activity: string;
messages_sent: number;
messages_received: number;
proposals_made: number;
conflicts_detected: number;
blocked_since?: string;
}
export interface CoordinationMetrics {
task_id: string;
start_time: string;
end_time?: string;
total_messages: number;
direct_messages: number;
blackboard_writes: number;
blackboard_reads: number;
conflicts_detected: number;
conflicts_resolved: number;
gamma_spawned: boolean;
gamma_spawn_reason?: string;
gamma_spawn_time?: string;
final_consensus: boolean;
performance_score: number;
}
export interface SpawnCondition {
type: "STUCK" | "CONFLICT" | "COMPLEXITY" | "SUCCESS" | "MANUAL";
threshold: number;
current_value: number;
triggered: boolean;
description: string;
}
export interface TaskDefinition {
task_id: string;
objective: string;
complexity: "low" | "medium" | "high" | "extreme";
subtasks: {
id: string;
description: string;
assigned_to?: AgentRole;
status: "pending" | "in_progress" | "completed" | "blocked";
dependencies: string[];
}[];
constraints: string[];
success_criteria: string[];
timeout_seconds: number;
}
export interface ConsensusVote {
agent: AgentRole;
proposal_id: string;
vote: "ACCEPT" | "REJECT" | "ABSTAIN";
reasoning: string;
timestamp: string;
}

View File

@ -0,0 +1,34 @@
# Tier0 Agent
> Observer-tier agent (read-only)
## Overview
This directory contains observer-tier agent (read-only).
## Key Files
| File | Description |
|------|-------------|
| `agent.py` | Python module |
| `run-agent.sh` | Shell script |
| `bootstrap.sh` | Shell script |
## Interfaces / APIs
*Document any APIs, CLI commands, or interfaces here.*
## Status
** Not Started**
See [STATUS.md](./STATUS.md) for detailed progress tracking.
## Architecture Reference
Part of the [Agent Governance System](/opt/agent-governance/docs/ARCHITECTURE.md).
Parent: [Agents](..)
---
*Last updated: 2026-01-23 23:25:09 UTC*

View File

@ -0,0 +1,30 @@
# Status: Tier0 Agent
## 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*

603
agents/tier0-agent/agent.py Executable file
View File

@ -0,0 +1,603 @@
#!/usr/bin/env python3
"""
Tier 0 Observer Agent
=====================
A governed agent that can read documentation, view inventory,
and generate plans, but CANNOT execute any commands.
This agent enforces strict Tier 0 constraints:
- Read-only file access (within allowed paths)
- Plan generation only (no execution)
- No secret access
- No SSH/API access
- All actions logged to governance ledger
"""
import json
import os
import sys
import hashlib
import sqlite3
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional, Any
import redis
# =============================================================================
# Configuration
# =============================================================================
AGENT_DIR = Path(__file__).parent
CONFIG_FILE = AGENT_DIR / "config" / "agent.json"
WORKSPACE_DIR = AGENT_DIR / "workspace"
PLANS_DIR = AGENT_DIR / "plans"
LOGS_DIR = AGENT_DIR / "logs"
LEDGER_DB = Path("/opt/agent-governance/ledger/governance.db")
# Load agent config
with open(CONFIG_FILE) as f:
CONFIG = json.load(f)
AGENT_ID = CONFIG["agent_id"]
AGENT_TIER = CONFIG["tier"]
ALLOWED_PATHS = [Path(p) for p in CONFIG["constraints"]["allowed_paths"]]
FORBIDDEN_PATHS = CONFIG["constraints"]["forbidden_paths"]
ALLOWED_ACTIONS = CONFIG["constraints"]["allowed_actions"]
FORBIDDEN_ACTIONS = CONFIG["constraints"]["forbidden_actions"]
# =============================================================================
# Data Classes
# =============================================================================
@dataclass
class ActionResult:
"""Result of an agent action"""
action: str
success: bool
data: Any = None
error: Optional[str] = None
blocked: bool = False
block_reason: Optional[str] = None
@dataclass
class Plan:
"""A generated plan"""
plan_id: str
title: str
description: str
target: str
steps: list
rollback_steps: list
created_at: str
agent_id: str
status: str = "draft"
# =============================================================================
# Governance Integration
# =============================================================================
class GovernanceClient:
"""Interfaces with the governance system"""
def __init__(self):
self.redis = self._get_redis()
self.session_id = os.environ.get("SESSION_ID", "unknown")
def _get_redis(self) -> Optional[redis.Redis]:
try:
# Get password from environment or file
password = os.environ.get("REDIS_PASSWORD")
if not password:
# Try to get from Vault (using root token for bootstrap)
import subprocess
with open("/opt/vault/init-keys.json") as f:
token = json.load(f)["root_token"]
result = subprocess.run([
"curl", "-sk",
"-H", f"X-Vault-Token: {token}",
"https://127.0.0.1:8200/v1/secret/data/services/dragonfly"
], capture_output=True, text=True)
creds = json.loads(result.stdout)["data"]["data"]
password = creds["password"]
return redis.Redis(host="127.0.0.1", port=6379, password=password, decode_responses=True)
except:
return None
def log_action(self, action: str, decision: str, target: str,
success: bool, confidence: float = 1.0, error: str = None):
"""Log action to governance ledger"""
try:
conn = sqlite3.connect(LEDGER_DB)
cursor = conn.cursor()
cursor.execute("""
INSERT INTO agent_actions
(timestamp, agent_id, agent_version, tier, action, decision,
confidence, target, success, error_message, session_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (
datetime.now(timezone.utc).isoformat(),
AGENT_ID,
CONFIG.get("agent_version", "1.0.0"),
AGENT_TIER,
action,
decision,
confidence,
target,
1 if success else 0,
error,
self.session_id
))
conn.commit()
conn.close()
except Exception as e:
print(f"Warning: Could not log action: {e}")
def update_heartbeat(self):
"""Update agent heartbeat in DragonflyDB"""
if self.redis:
try:
self.redis.set(f"agent:{AGENT_ID}:heartbeat", str(int(datetime.now().timestamp())), ex=60)
except:
pass
def check_revocation(self) -> bool:
"""Check if agent has been revoked"""
if self.redis:
try:
signal = self.redis.get(f"agent:{AGENT_ID}:revoke_signal")
return signal == "1"
except:
pass
return False
def increment_compliant(self):
"""Increment compliant run counter"""
try:
conn = sqlite3.connect(LEDGER_DB)
cursor = conn.cursor()
cursor.execute("""
UPDATE agent_metrics
SET compliant_runs = compliant_runs + 1,
consecutive_compliant = consecutive_compliant + 1,
total_runs = total_runs + 1,
last_active_at = datetime('now'),
updated_at = datetime('now')
WHERE agent_id = ?
""", (AGENT_ID,))
conn.commit()
conn.close()
except Exception as e:
print(f"Warning: Could not update metrics: {e}")
# =============================================================================
# Tier 0 Agent
# =============================================================================
class Tier0Agent:
"""
A strictly constrained Tier 0 agent.
Can only read and generate plans, cannot execute anything.
"""
def __init__(self):
self.governance = GovernanceClient()
self._check_not_revoked()
def _now(self) -> str:
return datetime.now(timezone.utc).isoformat()
def _check_not_revoked(self):
"""Check revocation status before any action"""
if self.governance.check_revocation():
print("[REVOKED] Agent has been revoked. Exiting.")
sys.exit(1)
def _is_path_allowed(self, path: str) -> bool:
"""Check if path is within allowed paths"""
target = Path(path).resolve()
# Check forbidden patterns
for pattern in FORBIDDEN_PATHS:
if pattern.startswith("**/"):
if pattern[3:] in str(target):
return False
elif target.match(pattern):
return False
# Check allowed paths
for allowed in ALLOWED_PATHS:
allowed_resolved = Path(allowed).resolve()
try:
target.relative_to(allowed_resolved)
return True
except ValueError:
continue
return False
def _block_action(self, action: str, reason: str) -> ActionResult:
"""Record a blocked action"""
self.governance.log_action(
action=action,
decision="BLOCKED",
target="N/A",
success=False,
error=reason
)
return ActionResult(
action=action,
success=False,
blocked=True,
block_reason=reason
)
# -------------------------------------------------------------------------
# Allowed Actions
# -------------------------------------------------------------------------
def read_file(self, path: str) -> ActionResult:
"""Read a file (if allowed)"""
self._check_not_revoked()
self.governance.update_heartbeat()
if not self._is_path_allowed(path):
return self._block_action("read_file", f"Path not allowed: {path}")
try:
with open(path) as f:
content = f.read()
self.governance.log_action(
action="read_file",
decision="EXECUTE",
target=path,
success=True
)
return ActionResult(
action="read_file",
success=True,
data={"path": path, "content": content, "size": len(content)}
)
except Exception as e:
self.governance.log_action(
action="read_file",
decision="EXECUTE",
target=path,
success=False,
error=str(e)
)
return ActionResult(action="read_file", success=False, error=str(e))
def list_directory(self, path: str) -> ActionResult:
"""List directory contents (if allowed)"""
self._check_not_revoked()
self.governance.update_heartbeat()
if not self._is_path_allowed(path):
return self._block_action("list_directory", f"Path not allowed: {path}")
try:
entries = []
for entry in Path(path).iterdir():
entries.append({
"name": entry.name,
"is_dir": entry.is_dir(),
"size": entry.stat().st_size if entry.is_file() else 0
})
self.governance.log_action(
action="list_directory",
decision="EXECUTE",
target=path,
success=True
)
return ActionResult(
action="list_directory",
success=True,
data={"path": path, "entries": entries}
)
except Exception as e:
return ActionResult(action="list_directory", success=False, error=str(e))
def generate_plan(self, title: str, description: str, target: str,
steps: list, rollback_steps: list = None) -> ActionResult:
"""Generate a plan (does NOT execute it)"""
self._check_not_revoked()
self.governance.update_heartbeat()
# Generate plan ID
plan_id = f"plan-{datetime.now().strftime('%Y%m%d-%H%M%S')}-{hashlib.sha256(title.encode()).hexdigest()[:8]}"
plan = Plan(
plan_id=plan_id,
title=title,
description=description,
target=target,
steps=steps,
rollback_steps=rollback_steps or [],
created_at=self._now(),
agent_id=AGENT_ID,
status="draft"
)
# Save plan to file
plan_file = PLANS_DIR / f"{plan_id}.json"
plan_dict = {
"plan_id": plan.plan_id,
"title": plan.title,
"description": plan.description,
"target": plan.target,
"steps": plan.steps,
"rollback_steps": plan.rollback_steps,
"created_at": plan.created_at,
"agent_id": plan.agent_id,
"agent_tier": AGENT_TIER,
"status": plan.status,
"requires_approval": True,
"approved_by": None,
"executed": False
}
with open(plan_file, "w") as f:
json.dump(plan_dict, f, indent=2)
# Log action
self.governance.log_action(
action="generate_plan",
decision="PLAN",
target=target,
success=True,
confidence=0.9
)
# Increment compliant counter
self.governance.increment_compliant()
return ActionResult(
action="generate_plan",
success=True,
data={
"plan_id": plan_id,
"plan_file": str(plan_file),
"message": "Plan generated. Requires approval before execution."
}
)
def request_review(self, subject: str, details: str) -> ActionResult:
"""Request human review/assistance"""
self._check_not_revoked()
self.governance.update_heartbeat()
review_id = f"review-{datetime.now().strftime('%Y%m%d-%H%M%S')}"
review_request = {
"review_id": review_id,
"agent_id": AGENT_ID,
"agent_tier": AGENT_TIER,
"subject": subject,
"details": details,
"created_at": self._now(),
"status": "pending"
}
# Save review request
review_file = WORKSPACE_DIR / f"{review_id}.json"
with open(review_file, "w") as f:
json.dump(review_request, f, indent=2)
self.governance.log_action(
action="request_review",
decision="PLAN",
target=subject,
success=True
)
return ActionResult(
action="request_review",
success=True,
data={"review_id": review_id, "message": "Review request submitted."}
)
# -------------------------------------------------------------------------
# Forbidden Actions (Always Blocked)
# -------------------------------------------------------------------------
def execute_command(self, command: str) -> ActionResult:
"""FORBIDDEN: Execute a command"""
return self._block_action(
"execute_command",
"Tier 0 agents cannot execute commands. Generate a plan instead."
)
def write_file(self, path: str, content: str) -> ActionResult:
"""FORBIDDEN: Write to a file (except plans in allowed paths)"""
# Allow writing to plans directory
if str(Path(path).resolve()).startswith(str(PLANS_DIR.resolve())):
try:
with open(path, "w") as f:
f.write(content)
self.governance.log_action(
action="write_plan_file",
decision="EXECUTE",
target=path,
success=True
)
return ActionResult(action="write_file", success=True, data={"path": path})
except Exception as e:
return ActionResult(action="write_file", success=False, error=str(e))
return self._block_action(
"write_file",
"Tier 0 agents cannot write files outside plans directory."
)
def ssh_connect(self, host: str) -> ActionResult:
"""FORBIDDEN: SSH to a host"""
return self._block_action(
"ssh_connect",
"Tier 0 agents cannot SSH to hosts. Generate a plan instead."
)
def terraform_apply(self, directory: str) -> ActionResult:
"""FORBIDDEN: Apply Terraform"""
return self._block_action(
"terraform_apply",
"Tier 0 agents cannot apply Terraform. Use terraform_plan to generate a plan."
)
def ansible_run(self, playbook: str) -> ActionResult:
"""FORBIDDEN: Run Ansible playbook"""
return self._block_action(
"ansible_run",
"Tier 0 agents cannot run Ansible. Generate a plan with check-mode only."
)
# =============================================================================
# CLI Interface
# =============================================================================
def main():
import argparse
parser = argparse.ArgumentParser(description="Tier 0 Observer Agent")
subparsers = parser.add_subparsers(dest="command", required=True)
# Status
subparsers.add_parser("status", help="Show agent status")
# Read file
read_parser = subparsers.add_parser("read", help="Read a file")
read_parser.add_argument("path", help="File path to read")
# List directory
ls_parser = subparsers.add_parser("ls", help="List directory")
ls_parser.add_argument("path", nargs="?", default=str(WORKSPACE_DIR))
# Generate plan
plan_parser = subparsers.add_parser("plan", help="Generate a plan")
plan_parser.add_argument("--title", required=True)
plan_parser.add_argument("--description", required=True)
plan_parser.add_argument("--target", required=True)
plan_parser.add_argument("--steps", required=True, help="JSON array of steps")
plan_parser.add_argument("--rollback", help="JSON array of rollback steps")
# Request review
review_parser = subparsers.add_parser("review", help="Request human review")
review_parser.add_argument("--subject", required=True)
review_parser.add_argument("--details", required=True)
# Test forbidden actions
subparsers.add_parser("test-forbidden", help="Test that forbidden actions are blocked")
args = parser.parse_args()
agent = Tier0Agent()
if args.command == "status":
print(f"\n{'='*50}")
print("TIER 0 AGENT STATUS")
print(f"{'='*50}")
print(f"Agent ID: {AGENT_ID}")
print(f"Tier: {AGENT_TIER} (Observer)")
print(f"Session: {os.environ.get('SESSION_ID', 'N/A')}")
print(f"\nAllowed Actions: {', '.join(ALLOWED_ACTIONS)}")
print(f"Forbidden Actions: {', '.join(FORBIDDEN_ACTIONS)}")
print(f"\nWorkspace: {WORKSPACE_DIR}")
print(f"Plans: {PLANS_DIR}")
# Check revocation
if agent.governance.check_revocation():
print(f"\n[REVOKED] Agent has been revoked!")
else:
print(f"\n[ACTIVE] Agent is active")
print(f"{'='*50}")
elif args.command == "read":
result = agent.read_file(args.path)
if result.success:
print(result.data["content"])
elif result.blocked:
print(f"[BLOCKED] {result.block_reason}")
else:
print(f"[ERROR] {result.error}")
elif args.command == "ls":
result = agent.list_directory(args.path)
if result.success:
for entry in result.data["entries"]:
prefix = "d" if entry["is_dir"] else "-"
print(f"{prefix} {entry['name']}")
elif result.blocked:
print(f"[BLOCKED] {result.block_reason}")
else:
print(f"[ERROR] {result.error}")
elif args.command == "plan":
steps = json.loads(args.steps)
rollback = json.loads(args.rollback) if args.rollback else []
result = agent.generate_plan(
title=args.title,
description=args.description,
target=args.target,
steps=steps,
rollback_steps=rollback
)
if result.success:
print(f"\n[OK] Plan generated: {result.data['plan_id']}")
print(f"File: {result.data['plan_file']}")
print(f"Note: {result.data['message']}")
else:
print(f"[ERROR] {result.error}")
elif args.command == "review":
result = agent.request_review(args.subject, args.details)
if result.success:
print(f"[OK] Review request: {result.data['review_id']}")
else:
print(f"[ERROR] {result.error}")
elif args.command == "test-forbidden":
print("\n" + "="*50)
print("TESTING FORBIDDEN ACTIONS")
print("="*50)
tests = [
("execute_command", lambda: agent.execute_command("ls -la")),
("write_file", lambda: agent.write_file("/etc/passwd", "test")),
("ssh_connect", lambda: agent.ssh_connect("10.77.10.1")),
("terraform_apply", lambda: agent.terraform_apply("./infra")),
("ansible_run", lambda: agent.ansible_run("playbook.yml")),
]
all_blocked = True
for name, test_fn in tests:
result = test_fn()
if result.blocked:
print(f"[BLOCKED] {name}: {result.block_reason}")
else:
print(f"[FAIL] {name} was NOT blocked!")
all_blocked = False
print("="*50)
if all_blocked:
print("[OK] All forbidden actions correctly blocked")
else:
print("[FAIL] Some actions were not blocked!")
sys.exit(1)
if __name__ == "__main__":
main()

218
agents/tier0-agent/bootstrap.sh Executable file
View File

@ -0,0 +1,218 @@
#!/bin/bash
#
# Tier 0 Agent Bootstrap Script
# =============================
# Authenticates with Vault, obtains scoped token, initializes agent environment.
#
# Usage:
# ./bootstrap.sh
# source ./bootstrap.sh # To export VAULT_TOKEN to current shell
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_DIR="${SCRIPT_DIR}/config"
CREDS_DIR="${SCRIPT_DIR}/credentials"
LOGS_DIR="${SCRIPT_DIR}/logs"
WORKSPACE_DIR="${SCRIPT_DIR}/workspace"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_ok() { echo -e "${GREEN}[OK]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# -----------------------------------------------------------------------------
# Configuration
# -----------------------------------------------------------------------------
VAULT_ADDR="${VAULT_ADDR:-https://127.0.0.1:8200}"
AGENT_CONFIG="${CONFIG_DIR}/agent.json"
APPROLE_CREDS="${CREDS_DIR}/approle.json"
# Read agent config
if [[ ! -f "${AGENT_CONFIG}" ]]; then
log_error "Agent config not found: ${AGENT_CONFIG}"
exit 1
fi
AGENT_ID=$(python3 -c "import json; print(json.load(open('${AGENT_CONFIG}'))['agent_id'])")
AGENT_TIER=$(python3 -c "import json; print(json.load(open('${AGENT_CONFIG}'))['tier'])")
echo ""
echo "=========================================="
echo "TIER 0 AGENT BOOTSTRAP"
echo "=========================================="
echo "Agent ID: ${AGENT_ID}"
echo "Tier: ${AGENT_TIER}"
echo "Vault: ${VAULT_ADDR}"
echo ""
# -----------------------------------------------------------------------------
# Step 1: Vault Authentication
# -----------------------------------------------------------------------------
log_info "Authenticating with Vault..."
if [[ ! -f "${APPROLE_CREDS}" ]]; then
log_error "AppRole credentials not found: ${APPROLE_CREDS}"
exit 1
fi
ROLE_ID=$(python3 -c "import json; print(json.load(open('${APPROLE_CREDS}'))['role_id'])")
SECRET_ID=$(python3 -c "import json; print(json.load(open('${APPROLE_CREDS}'))['secret_id'])")
# Login to Vault
LOGIN_RESPONSE=$(curl -sk -X POST \
-d "{\"role_id\":\"${ROLE_ID}\",\"secret_id\":\"${SECRET_ID}\"}" \
"${VAULT_ADDR}/v1/auth/approle/login")
# Check for errors
if echo "${LOGIN_RESPONSE}" | grep -q '"errors"'; then
log_error "Vault login failed:"
echo "${LOGIN_RESPONSE}" | python3 -m json.tool
exit 1
fi
# Extract token
VAULT_TOKEN=$(echo "${LOGIN_RESPONSE}" | python3 -c "import sys,json; print(json.load(sys.stdin)['auth']['client_token'])")
TOKEN_TTL=$(echo "${LOGIN_RESPONSE}" | python3 -c "import sys,json; print(json.load(sys.stdin)['auth']['lease_duration'])")
TOKEN_POLICIES=$(echo "${LOGIN_RESPONSE}" | python3 -c "import sys,json; print(','.join(json.load(sys.stdin)['auth']['policies']))")
log_ok "Vault authentication successful"
echo " Token TTL: ${TOKEN_TTL}s"
echo " Policies: ${TOKEN_POLICIES}"
# Export token
export VAULT_TOKEN
export VAULT_ADDR
# Save token to file for agent processes
echo "${VAULT_TOKEN}" > "${CREDS_DIR}/.token"
chmod 600 "${CREDS_DIR}/.token"
# -----------------------------------------------------------------------------
# Step 2: Verify Token Permissions
# -----------------------------------------------------------------------------
log_info "Verifying token permissions..."
# Test token lookup (should work with agent-self-read)
TOKEN_INFO=$(curl -sk -H "X-Vault-Token: ${VAULT_TOKEN}" \
"${VAULT_ADDR}/v1/auth/token/lookup-self" 2>/dev/null || echo '{"errors":["failed"]}')
if echo "${TOKEN_INFO}" | grep -q '"errors"'; then
log_warn "Could not verify token (agent-self-read may not be attached)"
else
log_ok "Token self-lookup successful"
fi
# Test that we CANNOT access secrets (Tier 0 should be denied)
SECRETS_TEST=$(curl -sk -H "X-Vault-Token: ${VAULT_TOKEN}" \
"${VAULT_ADDR}/v1/secret/data/services/dragonfly" 2>/dev/null || echo '{}')
if echo "${SECRETS_TEST}" | grep -q '"data"'; then
log_error "SECURITY VIOLATION: Tier 0 token can access secrets!"
exit 1
else
log_ok "Confirmed: Cannot access secrets (as expected for Tier 0)"
fi
# Test that we CANNOT access SSH (Tier 0 should be denied)
SSH_TEST=$(curl -sk -H "X-Vault-Token: ${VAULT_TOKEN}" \
"${VAULT_ADDR}/v1/ssh/creds/sandbox-user" -X POST -d '{"ip":"10.77.10.1"}' 2>/dev/null || echo '{}')
if echo "${SSH_TEST}" | grep -q '"signed_key"'; then
log_error "SECURITY VIOLATION: Tier 0 token can get SSH credentials!"
exit 1
else
log_ok "Confirmed: Cannot access SSH credentials (as expected for Tier 0)"
fi
# -----------------------------------------------------------------------------
# Step 3: Initialize Agent Environment
# -----------------------------------------------------------------------------
log_info "Initializing agent environment..."
# Export agent environment variables
export AGENT_ID
export AGENT_TIER
export AGENT_CONFIG
export AGENT_WORKSPACE="${WORKSPACE_DIR}"
export AGENT_LOGS="${LOGS_DIR}"
# Create session ID
export SESSION_ID="sess-$(date +%Y%m%d-%H%M%S)-$(openssl rand -hex 4)"
echo "${SESSION_ID}" > "${WORKSPACE_DIR}/.session_id"
# Initialize log file
LOG_FILE="${LOGS_DIR}/agent-${SESSION_ID}.log"
echo "=== Agent Session Started ===" > "${LOG_FILE}"
echo "Session ID: ${SESSION_ID}" >> "${LOG_FILE}"
echo "Agent ID: ${AGENT_ID}" >> "${LOG_FILE}"
echo "Tier: ${AGENT_TIER}" >> "${LOG_FILE}"
echo "Timestamp: $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "${LOG_FILE}"
echo "" >> "${LOG_FILE}"
log_ok "Agent environment initialized"
echo " Session: ${SESSION_ID}"
echo " Workspace: ${WORKSPACE_DIR}"
echo " Logs: ${LOG_FILE}"
# -----------------------------------------------------------------------------
# Step 4: Register with Governance System
# -----------------------------------------------------------------------------
log_info "Registering with governance system..."
# Register in DragonflyDB
DRAGONFLY_CREDS=$(curl -sk -H "X-Vault-Token: $(cat /opt/vault/init-keys.json | python3 -c "import sys,json; print(json.load(sys.stdin)['root_token'])")" \
"${VAULT_ADDR}/v1/secret/data/services/dragonfly" 2>/dev/null || echo '{}')
if echo "${DRAGONFLY_CREDS}" | grep -q '"password"'; then
REDIS_PASS=$(echo "${DRAGONFLY_CREDS}" | python3 -c "import sys,json; print(json.load(sys.stdin)['data']['data']['password'])")
# Register agent state
redis-cli -p 6379 -a "${REDIS_PASS}" SET "agent:${AGENT_ID}:state" "{\"agent_id\":\"${AGENT_ID}\",\"tier\":${AGENT_TIER},\"status\":\"RUNNING\",\"session_id\":\"${SESSION_ID}\",\"started_at\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\"}" 2>/dev/null
# Set heartbeat
redis-cli -p 6379 -a "${REDIS_PASS}" SET "agent:${AGENT_ID}:heartbeat" "$(date +%s)" EX 60 2>/dev/null
log_ok "Registered with governance system"
else
log_warn "Could not register with DragonflyDB (non-fatal)"
fi
# Register in SQLite ledger
sqlite3 /opt/agent-governance/ledger/governance.db "
INSERT OR REPLACE INTO agent_metrics (agent_id, current_tier, compliant_runs, consecutive_compliant, total_runs, updated_at)
VALUES ('${AGENT_ID}', ${AGENT_TIER}, 0, 0, 0, datetime('now'));
" 2>/dev/null || log_warn "Could not update ledger (non-fatal)"
# -----------------------------------------------------------------------------
# Done
# -----------------------------------------------------------------------------
echo ""
echo "=========================================="
echo "BOOTSTRAP COMPLETE"
echo "=========================================="
echo "Agent ${AGENT_ID} is ready (Tier ${AGENT_TIER})"
echo ""
echo "Environment variables set:"
echo " VAULT_TOKEN (use for Vault API calls)"
echo " VAULT_ADDR = ${VAULT_ADDR}"
echo " AGENT_ID = ${AGENT_ID}"
echo " AGENT_TIER = ${AGENT_TIER}"
echo " SESSION_ID = ${SESSION_ID}"
echo ""
echo "Next: Run the agent with ./run-agent.sh"
echo "=========================================="

View File

@ -0,0 +1,32 @@
# Config
> Observer-tier agent (read-only) - config submodule
## Overview
This directory contains observer-tier agent (read-only) - config submodule.
## Key Files
| File | Description |
|------|-------------|
| `agent.json` | Data/Schema |
## Interfaces / APIs
*Document any APIs, CLI commands, or interfaces here.*
## Status
** Not Started**
See [STATUS.md](./STATUS.md) for detailed progress tracking.
## Architecture Reference
Part of the [Agent Governance System](/opt/agent-governance/docs/ARCHITECTURE.md).
Parent: [Tier0 Agent](..)
---
*Last updated: 2026-01-23 23:25:09 UTC*

View File

@ -0,0 +1,30 @@
# Status: Config
## 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*

View File

@ -0,0 +1,81 @@
{
"agent_id": "tier0-agent-001",
"agent_version": "1.0.0",
"tier": 0,
"tier_name": "Observer",
"description": "Tier 0 Observer Agent - Read-only access, plan generation only",
"capabilities": {
"read_inventory": true,
"read_documentation": true,
"generate_plans": true,
"execute_commands": false,
"modify_files": false,
"access_secrets": false,
"ssh_access": false,
"api_access": false
},
"constraints": {
"allowed_actions": [
"read_file",
"list_directory",
"search_code",
"generate_plan",
"request_review"
],
"forbidden_actions": [
"execute_command",
"write_file",
"delete_file",
"ssh_connect",
"api_call",
"terraform_apply",
"ansible_run"
],
"allowed_paths": [
"/opt/agent-governance/docs/",
"/opt/agent-governance/inventory/",
"/opt/agent-governance/agents/tier0-agent/workspace/",
"/opt/agent-governance/agents/tier0-agent/plans/"
],
"forbidden_paths": [
"/opt/vault/",
"/etc/",
"/root/",
"**/.env",
"**/credentials*",
"**/secrets*"
]
},
"vault": {
"auth_method": "approle",
"role_name": "tier0-agent",
"token_ttl": "1h",
"token_max_ttl": "4h",
"policies": ["t0-observer", "agent-self-read"]
},
"governance": {
"preflight_required": true,
"plan_approval_required": true,
"evidence_required": true,
"heartbeat_interval": 30,
"error_budget": {
"max_total_errors": 5,
"max_same_error_repeats": 2
}
},
"promotion": {
"target_tier": 1,
"requirements": {
"min_compliant_runs": 5,
"min_consecutive_compliant": 3,
"required_actions": ["generate_plan"],
"max_violations_30d": 0
}
}
}

View File

@ -0,0 +1 @@
hvs.CAESIDquQkRp_KXaVTn_7qjDbDN_2cxHW56RI7hJ0LvS9iqNGh4KHGh2cy5vOWVoTUVkN0VOVEVSd2hvWGlFWnY3bXg

View File

@ -0,0 +1,8 @@
{
"role_id": "95f5ab5b-e79b-05f3-6019-53cd86fa0e38",
"secret_id": "26691ba6-b6f0-854d-e1ae-c32620393dae",
"secret_id_accessor": "8df870a9-78ca-3c6e-059b-40deefbd0c82",
"created_at": "2026-01-23T20:13:00Z",
"expires_at": "2026-01-24T20:13:00Z",
"note": "Regenerate secret_id before expiry with: vault write -f auth/approle/role/tier0-agent/secret-id"
}

View File

@ -0,0 +1,37 @@
# Plans
> Observer-tier agent (read-only) - plans submodule
## Overview
This directory contains observer-tier agent (read-only) - plans submodule.
## Key Files
| File | Description |
|------|-------------|
| `plan-20260123-214700-spark-logging-pipeline.json` | Data/Schema |
| `plan-20260123-163928-bbd5dfb1.json` | Data/Schema |
| `plan-20260123-163935-51ed67f4.json` | Data/Schema |
| `plan-20260123-160942-93e4ab29.json` | Data/Schema |
| `plan-20260123-151945-1a7acb76.json` | Data/Schema |
| `plan-20260123-163921-2fbea5fc.json` | Data/Schema |
## Interfaces / APIs
*Document any APIs, CLI commands, or interfaces here.*
## Status
** Not Started**
See [STATUS.md](./STATUS.md) for detailed progress tracking.
## Architecture Reference
Part of the [Agent Governance System](/opt/agent-governance/docs/ARCHITECTURE.md).
Parent: [Tier0 Agent](..)
---
*Last updated: 2026-01-23 23:25:09 UTC*

View File

@ -0,0 +1,30 @@
# Status: Plans
## 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*

View File

@ -0,0 +1,24 @@
{
"plan_id": "plan-20260123-151945-1a7acb76",
"title": "Setup Nginx on sandbox-vm-01",
"description": "Install and configure Nginx web server on sandbox VM for testing",
"target": "sandbox-vm-01",
"steps": [
"Install nginx package",
"Configure default site",
"Enable and start nginx service",
"Verify nginx is running"
],
"rollback_steps": [
"Stop nginx service",
"Remove nginx package",
"Clean up config files"
],
"created_at": "2026-01-23T20:19:45.941428+00:00",
"agent_id": "tier0-agent-001",
"agent_tier": 0,
"status": "draft",
"requires_approval": true,
"approved_by": null,
"executed": false
}

View File

@ -0,0 +1,54 @@
{
"plan_id": "plan-20260123-160942-93e4ab29",
"title": "Deploy Apache Spark in Docker",
"description": "Deploy Apache Spark cluster in Docker with web UI exposed on port 9944",
"target": "localhost",
"steps": [
{
"action": "pull_image",
"description": "Pull Apache Spark Docker image",
"command": "docker pull apache/spark:latest"
},
{
"action": "create_network",
"description": "Create Docker network for Spark cluster",
"command": "docker network create spark-net"
},
{
"action": "run_master",
"description": "Start Spark master node with UI on port 9944",
"command": "docker run -d --name spark-master --network spark-net -p 9944:8080 -p 7077:7077 apache/spark:latest /opt/spark/bin/spark-class org.apache.spark.deploy.master.Master"
},
{
"action": "run_worker",
"description": "Start Spark worker node",
"command": "docker run -d --name spark-worker --network spark-net apache/spark:latest /opt/spark/bin/spark-class org.apache.spark.deploy.worker.Worker spark://spark-master:7077"
},
{
"action": "verify",
"description": "Verify Spark UI is accessible",
"command": "curl -s http://localhost:9944 | grep -q Spark"
}
],
"rollback_steps": [
{
"action": "stop_worker",
"command": "docker stop spark-worker && docker rm spark-worker"
},
{
"action": "stop_master",
"command": "docker stop spark-master && docker rm spark-master"
},
{
"action": "remove_network",
"command": "docker network rm spark-net"
}
],
"created_at": "2026-01-23T21:09:42.432257+00:00",
"agent_id": "tier0-agent-001",
"agent_tier": 0,
"status": "approved",
"requires_approval": true,
"approved_by": "human-operator",
"executed": true
}

View File

@ -0,0 +1,40 @@
{
"plan_id": "plan-20260123-163921-2fbea5fc",
"title": "Deploy Redis Cache",
"description": "Deploy Redis cache server in Docker for application caching",
"target": "localhost",
"steps": [
{
"action": "pull_image",
"description": "Pull Redis image",
"command": "docker pull redis:7-alpine"
},
{
"action": "run_container",
"description": "Start Redis with persistence",
"command": "docker run -d --name redis-cache --network spark-net -p 6380:6379 -v redis-data:/data redis:7-alpine redis-server --appendonly yes"
},
{
"action": "verify",
"description": "Verify Redis responds to ping",
"command": "docker exec redis-cache redis-cli ping"
}
],
"rollback_steps": [
{
"action": "stop_container",
"command": "docker stop redis-cache && docker rm redis-cache"
},
{
"action": "remove_volume",
"command": "docker volume rm redis-data"
}
],
"created_at": "2026-01-23T21:39:21.352391+00:00",
"agent_id": "tier0-agent-001",
"agent_tier": 0,
"status": "approved",
"requires_approval": true,
"approved_by": "human-operator",
"executed": true
}

View File

@ -0,0 +1,41 @@
{
"plan_id": "plan-20260123-163928-bbd5dfb1",
"title": "Deploy Nginx Reverse Proxy",
"description": "Deploy Nginx as reverse proxy for Spark UI with basic auth",
"target": "localhost",
"steps": [
{
"action": "pull_image",
"description": "Pull Nginx image",
"command": "docker pull nginx:alpine"
},
{
"action": "create_config",
"description": "Create nginx config directory",
"command": "mkdir -p /opt/agent-governance/agents/tier0-agent/workspace/nginx"
},
{
"action": "run_container",
"description": "Start Nginx proxy",
"command": "docker run -d --name nginx-proxy --network spark-net -p 8080:80 nginx:alpine"
},
{
"action": "verify",
"description": "Verify Nginx responds",
"command": "curl -s http://localhost:8080 | grep -q nginx"
}
],
"rollback_steps": [
{
"action": "stop_container",
"command": "docker stop nginx-proxy && docker rm nginx-proxy"
}
],
"created_at": "2026-01-23T21:39:28.520158+00:00",
"agent_id": "tier0-agent-001",
"agent_tier": 0,
"status": "approved",
"requires_approval": true,
"approved_by": "human-operator",
"executed": true
}

View File

@ -0,0 +1,41 @@
{
"plan_id": "plan-20260123-163935-51ed67f4",
"title": "Deploy Prometheus Monitoring",
"description": "Deploy Prometheus for metrics collection from Spark cluster",
"target": "localhost",
"steps": [
{
"action": "pull_image",
"description": "Pull Prometheus image",
"command": "docker pull prom/prometheus:latest"
},
{
"action": "create_config_dir",
"description": "Create Prometheus config directory",
"command": "mkdir -p /opt/agent-governance/agents/tier0-agent/workspace/prometheus"
},
{
"action": "run_container",
"description": "Start Prometheus",
"command": "docker run -d --name prometheus --network spark-net -p 9090:9090 prom/prometheus:latest"
},
{
"action": "verify",
"description": "Verify Prometheus UI accessible",
"command": "curl -s http://localhost:9090/-/healthy"
}
],
"rollback_steps": [
{
"action": "stop_container",
"command": "docker stop prometheus && docker rm prometheus"
}
],
"created_at": "2026-01-23T21:39:35.251841+00:00",
"agent_id": "tier0-agent-001",
"agent_tier": 0,
"status": "approved",
"requires_approval": true,
"approved_by": "human-operator",
"executed": true
}

View File

@ -0,0 +1,403 @@
{
"plan_id": "plan-20260123-214700-spark-logging-pipeline",
"title": "Spark Cluster Logging Aggregation Pipeline",
"description": "Production-grade logging aggregation system for Spark cluster using Lambda architecture with Kafka, Flink, and ClickHouse",
"version": "1.0.0",
"type": "ground_truth",
"target": "localhost",
"source": {
"generated_by": "multi-agent-orchestrator",
"task_id": "task-zfwla3-mkretltd",
"model": "anthropic/claude-sonnet-4",
"proposal_id": "t2aiqzohmkreue73",
"evaluation": {
"score": 0.85,
"feasibility": 0.88,
"completeness": 0.82,
"evaluator": "BETA"
}
},
"architecture": {
"pattern": "Lambda Architecture",
"components": {
"ingestion": {
"technology": "Apache Kafka",
"purpose": "High-throughput log ingestion with partitioning",
"features": ["Schema Registry", "Topic-per-source", "Compression"]
},
"stream_processing": {
"technology": "Apache Flink",
"purpose": "Real-time processing, enrichment, and transformation",
"features": ["Checkpointing", "Exactly-once semantics", "Backpressure handling"]
},
"storage": {
"technology": "ClickHouse",
"purpose": "Columnar storage optimized for analytics queries",
"features": ["Materialized views", "ReplicatedMergeTree", "Tiered storage"]
},
"schema_management": {
"technology": "Confluent Schema Registry",
"purpose": "Schema evolution and format governance",
"formats": ["JSON", "Avro", "Protobuf"]
}
}
},
"requirements": {
"performance": {
"query_latency": "<100ms",
"ingestion_throughput": "high-volume",
"data_freshness": "near-real-time"
},
"constraints": [
"Must use open-source technologies where possible",
"Latency < 100ms for query responses",
"Support for multiple data formats (JSON, Avro, Protobuf)",
"Cost-effective for variable workloads"
],
"success_criteria": [
"Complete architecture design with component diagrams",
"Data flow specifications",
"Scalability analysis",
"Fault tolerance mechanisms documented",
"Cost estimation provided"
]
},
"phases": [
{
"phase_id": "phase-1-ingestion",
"name": "Design High-Performance Ingestion Layer",
"complexity": "high",
"estimated_effort": "3-4 days",
"owner": "ALPHA",
"key_decisions": [
"Partitioning strategy",
"Schema registry setup",
"Format conversion pipeline"
],
"steps": [
{
"step_id": "1.1",
"action": "deploy_kafka",
"description": "Deploy Kafka cluster with Zookeeper",
"command": "docker-compose -f kafka-cluster.yml up -d",
"artifacts": ["kafka-cluster.yml"]
},
{
"step_id": "1.2",
"action": "deploy_schema_registry",
"description": "Deploy Confluent Schema Registry",
"command": "docker run -d --name schema-registry --network spark-net -p 8081:8081 -e SCHEMA_REGISTRY_KAFKASTORE_BOOTSTRAP_SERVERS=kafka:9092 confluentinc/cp-schema-registry:latest"
},
{
"step_id": "1.3",
"action": "create_topics",
"description": "Create Kafka topics for Spark logs",
"command": "kafka-topics.sh --create --topic spark-driver-logs --partitions 12 --replication-factor 2 --bootstrap-server localhost:9092"
},
{
"step_id": "1.4",
"action": "configure_spark_log_forwarding",
"description": "Configure Spark to forward logs to Kafka",
"config": {
"spark.driver.extraJavaOptions": "-Dlog4j.configuration=file:/opt/spark/conf/log4j-kafka.properties",
"spark.executor.extraJavaOptions": "-Dlog4j.configuration=file:/opt/spark/conf/log4j-kafka.properties"
}
}
],
"verification": {
"command": "kafka-console-consumer.sh --topic spark-driver-logs --bootstrap-server localhost:9092 --max-messages 5",
"expected": "Log messages flowing"
}
},
{
"phase_id": "phase-2-storage",
"name": "Optimize Storage and Query Performance",
"complexity": "high",
"estimated_effort": "4-5 days",
"owner": "ALPHA",
"key_decisions": [
"Storage engine selection",
"Index design",
"Query routing logic"
],
"steps": [
{
"step_id": "2.1",
"action": "deploy_clickhouse",
"description": "Deploy ClickHouse cluster",
"command": "docker run -d --name clickhouse-server --network spark-net -p 8123:8123 -p 9000:9000 -v clickhouse-data:/var/lib/clickhouse clickhouse/clickhouse-server:latest"
},
{
"step_id": "2.2",
"action": "create_log_table",
"description": "Create optimized log table with ReplicatedMergeTree",
"sql": "CREATE TABLE spark_logs (timestamp DateTime64(3), level String, logger String, message String, spark_app_id String, executor_id String, host String) ENGINE = MergeTree() PARTITION BY toYYYYMMDD(timestamp) ORDER BY (spark_app_id, timestamp) TTL timestamp + INTERVAL 30 DAY"
},
{
"step_id": "2.3",
"action": "create_materialized_views",
"description": "Create materialized views for common query patterns",
"sql": "CREATE MATERIALIZED VIEW spark_logs_by_level ENGINE = SummingMergeTree() ORDER BY (level, toStartOfHour(timestamp)) AS SELECT level, toStartOfHour(timestamp) as hour, count() as count FROM spark_logs GROUP BY level, hour"
},
{
"step_id": "2.4",
"action": "configure_tiered_storage",
"description": "Configure hot-warm-cold tiered storage",
"config": {
"hot_tier": "7 days on NVMe SSD",
"warm_tier": "30 days on SSD",
"cold_tier": "90+ days on S3/object storage"
}
}
],
"verification": {
"command": "clickhouse-client --query 'SELECT count() FROM spark_logs'",
"expected": "Query returns in <100ms"
}
},
{
"phase_id": "phase-3-processing",
"name": "Implement Real-time Processing Pipeline",
"complexity": "medium-high",
"estimated_effort": "3-4 days",
"owner": "BETA",
"key_decisions": [
"Processing framework choice",
"State management",
"Backpressure handling"
],
"steps": [
{
"step_id": "3.1",
"action": "deploy_flink",
"description": "Deploy Apache Flink cluster",
"command": "docker run -d --name flink-jobmanager --network spark-net -p 8082:8081 flink:latest jobmanager"
},
{
"step_id": "3.2",
"action": "deploy_flink_taskmanager",
"description": "Deploy Flink TaskManager",
"command": "docker run -d --name flink-taskmanager --network spark-net flink:latest taskmanager"
},
{
"step_id": "3.3",
"action": "deploy_log_processor_job",
"description": "Deploy Flink job for log processing",
"job_config": {
"source": "Kafka spark-driver-logs topic",
"transformations": [
"Parse log format",
"Extract structured fields",
"Enrich with metadata",
"Handle format conversion (JSON/Avro/Protobuf)"
],
"sink": "ClickHouse spark_logs table",
"checkpointing": "10 seconds",
"parallelism": 4
}
}
],
"verification": {
"command": "curl http://localhost:8082/jobs",
"expected": "Log processor job RUNNING"
}
},
{
"phase_id": "phase-4-fault-tolerance",
"name": "Implement Comprehensive Fault Tolerance",
"complexity": "medium-high",
"estimated_effort": "3-4 days",
"owner": "BETA",
"key_decisions": [
"Replication strategies",
"Failure detection",
"Recovery procedures"
],
"steps": [
{
"step_id": "4.1",
"action": "configure_kafka_replication",
"description": "Configure Kafka replication and ISR",
"config": {
"replication.factor": 2,
"min.insync.replicas": 1,
"unclean.leader.election.enable": false
}
},
{
"step_id": "4.2",
"action": "configure_flink_checkpointing",
"description": "Enable Flink checkpointing for exactly-once",
"config": {
"execution.checkpointing.interval": "10s",
"execution.checkpointing.mode": "EXACTLY_ONCE",
"state.backend": "rocksdb",
"state.checkpoints.dir": "file:///opt/flink/checkpoints"
}
},
{
"step_id": "4.3",
"action": "configure_clickhouse_replication",
"description": "Configure ClickHouse replication",
"config": {
"engine": "ReplicatedMergeTree",
"zookeeper_path": "/clickhouse/tables/{shard}/spark_logs",
"replica_name": "{replica}"
}
},
{
"step_id": "4.4",
"action": "setup_dead_letter_queue",
"description": "Create DLQ for failed log entries",
"command": "kafka-topics.sh --create --topic spark-logs-dlq --partitions 3 --replication-factor 2 --bootstrap-server localhost:9092"
}
],
"verification": {
"test": "Kill one Kafka broker, verify no data loss",
"expected": "System continues processing with <5s recovery"
}
},
{
"phase_id": "phase-5-scaling",
"name": "Design Cost-Effective Scaling Strategy",
"complexity": "medium",
"estimated_effort": "2-3 days",
"owner": "BETA",
"key_decisions": [
"Tiering policies",
"Auto-scaling triggers",
"Resource allocation"
],
"steps": [
{
"step_id": "5.1",
"action": "configure_autoscaling",
"description": "Configure Kubernetes HPA for Flink",
"config": {
"min_replicas": 2,
"max_replicas": 10,
"target_cpu_utilization": 70,
"scale_up_stabilization": "60s",
"scale_down_stabilization": "300s"
}
},
{
"step_id": "5.2",
"action": "configure_kafka_tiering",
"description": "Enable Kafka tiered storage",
"config": {
"remote.log.storage.system.enable": true,
"remote.log.storage.manager.class.name": "org.apache.kafka.server.log.remote.storage.LocalTieredStorage",
"local.retention.ms": 86400000
}
},
{
"step_id": "5.3",
"action": "setup_monitoring",
"description": "Deploy Prometheus + Grafana monitoring",
"dashboards": [
"Kafka lag per topic/partition",
"Flink throughput and backpressure",
"ClickHouse query latency p50/p95/p99",
"Storage utilization per tier"
]
}
],
"verification": {
"test": "Generate 10x load spike",
"expected": "System scales up within 2 minutes, scales down within 10 minutes"
}
}
],
"rollback_strategy": {
"checkpoints": [
"After each phase completion",
"Before any destructive operation"
],
"procedures": [
{
"trigger": "Kafka cluster failure",
"action": "Failover to standby cluster, replay from checkpoint"
},
{
"trigger": "Flink job failure",
"action": "Restart from last checkpoint, resume processing"
},
{
"trigger": "ClickHouse corruption",
"action": "Restore from replica, rebuild materialized views"
},
{
"trigger": "Full system rollback",
"action": "Stop all components, restore from backup, replay Kafka from offset"
}
]
},
"monitoring": {
"metrics": [
{
"name": "ingestion_lag",
"source": "Kafka consumer lag",
"threshold": "<1000 messages",
"alert": "PagerDuty if >5000 for 5 minutes"
},
{
"name": "query_latency_p99",
"source": "ClickHouse query logs",
"threshold": "<100ms",
"alert": "Slack if >200ms for 1 minute"
},
{
"name": "processing_throughput",
"source": "Flink metrics",
"threshold": ">10000 events/sec",
"alert": "Email if <5000 for 10 minutes"
}
],
"dashboards": [
"System Overview",
"Kafka Health",
"Flink Jobs",
"ClickHouse Performance",
"Cost Analysis"
]
},
"cost_estimation": {
"infrastructure": {
"kafka_cluster": "$200-400/month (3 brokers)",
"flink_cluster": "$150-300/month (1 JM + 2 TM)",
"clickhouse_cluster": "$300-600/month (3 nodes)",
"monitoring": "$50-100/month"
},
"total_monthly": "$700-1400",
"optimization_notes": [
"Use spot instances for Flink TaskManagers",
"Tiered storage reduces ClickHouse costs by 40%",
"Auto-scaling minimizes idle capacity"
]
},
"strengths": [
"Excellent technology selection - Kafka/Flink/ClickHouse is a proven stack for log analytics",
"Sub-100ms query performance through ClickHouse's columnar engine and materialized views",
"Comprehensive tiered storage strategy optimizes costs while maintaining performance",
"Schema Registry integration provides robust data governance and evolution",
"Strong fault tolerance with Kafka replication and Flink checkpointing",
"Auto-scaling capabilities address variable load patterns effectively",
"Unified query API abstracts complexity from end users"
],
"known_limitations": [
"Lambda architecture complexity requires significant DevOps expertise",
"Data consistency reconciliation between real-time and batch layers needs monitoring",
"Flink memory management under high throughput scenarios needs careful tuning"
],
"implementation_notes": "Start with a pilot deployment focusing on a single log source to validate the architecture. Implement comprehensive monitoring before scaling. Pay special attention to Flink job tuning and ClickHouse table engine selection. Consider using ClickHouse's ReplicatedMergeTree for high availability. Plan for gradual migration from existing systems with parallel running during transition period.",
"created_at": "2026-01-23T21:47:00.000000+00:00",
"agent_id": "tier0-agent-001",
"agent_tier": 0,
"status": "approved",
"requires_approval": true,
"approved_by": "multi-agent-consensus",
"executed": false,
"priority": "high",
"tags": ["spark", "logging", "kafka", "flink", "clickhouse", "lambda-architecture", "ground-truth"]
}

20
agents/tier0-agent/run-agent.sh Executable file
View File

@ -0,0 +1,20 @@
#!/bin/bash
#
# Tier 0 Agent Runner
# ===================
# Bootstraps and runs the Tier 0 agent with full governance.
#
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Bootstrap first (sets up environment)
source "${SCRIPT_DIR}/bootstrap.sh"
echo ""
echo "Starting agent..."
echo ""
# Run agent command
python3 "${SCRIPT_DIR}/agent.py" "$@"

View File

@ -0,0 +1 @@
sess-20260123-160825-549d8bbb

View File

@ -0,0 +1,32 @@
# Tier1 Agent
> Executor-tier agent (infrastructure changes)
## Overview
This directory contains executor-tier agent (infrastructure changes).
## Key Files
| File | Description |
|------|-------------|
| *No files yet* | |
## Interfaces / APIs
*Document any APIs, CLI commands, or interfaces here.*
## Status
** Not Started**
See [STATUS.md](./STATUS.md) for detailed progress tracking.
## Architecture Reference
Part of the [Agent Governance System](/opt/agent-governance/docs/ARCHITECTURE.md).
Parent: [Agents](..)
---
*Last updated: 2026-01-23 23:25:09 UTC*

View File

@ -0,0 +1,30 @@
# Status: Tier1 Agent
## 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*

View File

@ -0,0 +1,32 @@
# Config
> Executor-tier agent (infrastructure changes) - config submodule
## Overview
This directory contains executor-tier agent (infrastructure changes) - config submodule.
## Key Files
| File | Description |
|------|-------------|
| `agent.json` | Data/Schema |
## Interfaces / APIs
*Document any APIs, CLI commands, or interfaces here.*
## Status
** Not Started**
See [STATUS.md](./STATUS.md) for detailed progress tracking.
## Architecture Reference
Part of the [Agent Governance System](/opt/agent-governance/docs/ARCHITECTURE.md).
Parent: [Tier1 Agent](..)
---
*Last updated: 2026-01-23 23:25:09 UTC*

View File

@ -0,0 +1,30 @@
# Status: Config
## 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*

View File

@ -0,0 +1,92 @@
{
"agent_id": "tier0-agent-001",
"agent_version": "1.0.0",
"tier": 1,
"tier_name": "Operator",
"promoted_from": 0,
"promoted_at": "2026-01-23T21:58:51+00:00",
"description": "Tier 1 Operator Agent - Sandbox execution, basic deployments",
"capabilities": {
"read_inventory": true,
"read_documentation": true,
"generate_plans": true,
"execute_commands": true,
"modify_files": true,
"access_secrets": false,
"ssh_access": true,
"api_access": true
},
"constraints": {
"allowed_actions": [
"read_file",
"list_directory",
"search_code",
"generate_plan",
"request_review",
"execute_command",
"write_file",
"ansible_check",
"ansible_run",
"terraform_plan",
"terraform_apply",
"docker_run"
],
"forbidden_actions": [
"delete_production",
"access_vault_root",
"modify_governance"
],
"allowed_targets": [
"localhost",
"sandbox-*"
],
"forbidden_targets": [
"prod-*",
"staging-db-*"
],
"allowed_paths": [
"/opt/agent-governance/docs/",
"/opt/agent-governance/inventory/",
"/opt/agent-governance/sandbox/",
"/opt/agent-governance/agents/tier1-agent/workspace/",
"/opt/agent-governance/agents/tier1-agent/plans/"
],
"forbidden_paths": [
"/opt/vault/init-keys.json",
"/etc/shadow",
"/root/.ssh/"
]
},
"vault": {
"auth_method": "approle",
"role_name": "tier1-agent",
"token_ttl": "30m",
"token_max_ttl": "2h",
"policies": ["t1-operator", "agent-self-read", "sandbox-access"]
},
"governance": {
"preflight_required": true,
"plan_approval_required": false,
"evidence_required": true,
"heartbeat_interval": 30,
"error_budget": {
"max_total_errors": 8,
"max_same_error_repeats": 3
}
},
"promotion": {
"target_tier": 2,
"requirements": {
"min_compliant_runs": 10,
"min_consecutive_compliant": 5,
"required_actions": ["ansible_run", "terraform_apply"],
"max_violations_30d": 0
}
}
}

View File

@ -0,0 +1,32 @@
# Plans
> Executor-tier agent (infrastructure changes) - plans submodule
## Overview
This directory contains executor-tier agent (infrastructure changes) - plans submodule.
## Key Files
| File | Description |
|------|-------------|
| *No files yet* | |
## Interfaces / APIs
*Document any APIs, CLI commands, or interfaces here.*
## Status
** Not Started**
See [STATUS.md](./STATUS.md) for detailed progress tracking.
## Architecture Reference
Part of the [Agent Governance System](/opt/agent-governance/docs/ARCHITECTURE.md).
Parent: [Tier1 Agent](..)
---
*Last updated: 2026-01-23 23:25:09 UTC*

View File

@ -0,0 +1,30 @@
# Status: Plans
## 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*

32
analytics/README.md Normal file
View File

@ -0,0 +1,32 @@
# Analytics
> Learning analytics and pattern detection
## Overview
This directory contains learning analytics and pattern detection.
## Key Files
| File | Description |
|------|-------------|
| `learning.py` | Python module |
## Interfaces / APIs
*Document any APIs, CLI commands, or interfaces here.*
## Status
** Not Started**
See [STATUS.md](./STATUS.md) for detailed progress tracking.
## Architecture Reference
Part of the [Agent Governance System](/opt/agent-governance/docs/ARCHITECTURE.md).
Parent: [Project Root](/opt/agent-governance)
---
*Last updated: 2026-01-23 23:25:09 UTC*

35
analytics/STATUS.md Normal file
View File

@ -0,0 +1,35 @@
# Status: Analytics
## 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:31 UTC
- **Phase**: COMPLETE
- **Action**: Learning analytics with patterns, predictions, optimizations
- **Details**: Phase updated to complete
### 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:31 UTC*

514
analytics/learning.py Normal file
View File

@ -0,0 +1,514 @@
#!/usr/bin/env python3
"""
Learning from History System
Analyzes past task completions to:
- Identify success/failure patterns
- Suggest optimizations
- Predict potential failures
- Recommend agent improvements
"""
import sqlite3
import json
import statistics
from collections import defaultdict
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict, List, Any, Optional, Tuple
import redis
LEDGER_PATH = Path("/opt/agent-governance/ledger/governance.db")
REDIS_HOST = "127.0.0.1"
REDIS_PORT = 6379
REDIS_PASSWORD = "governance2026"
@dataclass
class AgentStats:
"""Statistics for a single agent"""
agent_id: str
total_actions: int = 0
successful_actions: int = 0
failed_actions: int = 0
avg_confidence: float = 0.0
action_distribution: Dict[str, int] = field(default_factory=dict)
error_types: Dict[str, int] = field(default_factory=dict)
promotion_potential: float = 0.0
@dataclass
class Pattern:
"""A detected pattern in agent behavior"""
pattern_type: str
description: str
frequency: int
confidence: float
agents_affected: List[str]
recommendation: str
@dataclass
class Prediction:
"""A failure prediction"""
agent_id: str
risk_level: str # low, medium, high, critical
risk_score: float
factors: List[str]
recommended_actions: List[str]
class HistoryAnalyzer:
"""
Analyzes historical agent data to extract insights.
"""
def __init__(self):
self.conn = sqlite3.connect(LEDGER_PATH)
self.conn.row_factory = sqlite3.Row
self.redis = redis.Redis(
host=REDIS_HOST,
port=REDIS_PORT,
password=REDIS_PASSWORD,
decode_responses=True
)
def close(self):
self.conn.close()
def get_agent_stats(self, agent_id: str = None, days: int = 30) -> List[AgentStats]:
"""Get statistics for agent(s)"""
cutoff = (datetime.utcnow() - timedelta(days=days)).isoformat()
if agent_id:
query = """
SELECT agent_id, action, decision, confidence, success, error_type
FROM agent_actions
WHERE agent_id = ? AND created_at > ?
"""
cursor = self.conn.execute(query, (agent_id, cutoff))
else:
query = """
SELECT agent_id, action, decision, confidence, success, error_type
FROM agent_actions
WHERE created_at > ?
"""
cursor = self.conn.execute(query, (cutoff,))
# Aggregate by agent
agent_data = defaultdict(lambda: {
"actions": [],
"successes": 0,
"failures": 0,
"confidences": [],
"action_types": defaultdict(int),
"error_types": defaultdict(int)
})
for row in cursor:
aid = row["agent_id"]
data = agent_data[aid]
data["actions"].append(row)
data["confidences"].append(row["confidence"] or 0)
data["action_types"][row["action"]] += 1
if row["success"]:
data["successes"] += 1
else:
data["failures"] += 1
if row["error_type"]:
data["error_types"][row["error_type"]] += 1
# Build stats objects
stats = []
for aid, data in agent_data.items():
total = len(data["actions"])
success_rate = data["successes"] / total if total > 0 else 0
stats.append(AgentStats(
agent_id=aid,
total_actions=total,
successful_actions=data["successes"],
failed_actions=data["failures"],
avg_confidence=statistics.mean(data["confidences"]) if data["confidences"] else 0,
action_distribution=dict(data["action_types"]),
error_types=dict(data["error_types"]),
promotion_potential=self._calculate_promotion_potential(success_rate, total)
))
return stats
def _calculate_promotion_potential(self, success_rate: float, total_actions: int) -> float:
"""Calculate promotion potential score (0-1)"""
if total_actions < 5:
return 0.0
# Base on success rate (0-0.5) + volume (0-0.3) + consistency (0-0.2)
rate_score = min(success_rate, 1.0) * 0.5
volume_score = min(total_actions / 50, 1.0) * 0.3
consistency_score = 0.2 if success_rate > 0.9 else (0.1 if success_rate > 0.8 else 0)
return rate_score + volume_score + consistency_score
def detect_patterns(self, days: int = 30) -> List[Pattern]:
"""Detect patterns in agent behavior"""
patterns = []
# Pattern 1: Repeated failures
failure_agents = self._find_repeated_failures(days)
if failure_agents:
patterns.append(Pattern(
pattern_type="REPEATED_FAILURES",
description="Agents with multiple consecutive failures",
frequency=len(failure_agents),
confidence=0.9,
agents_affected=failure_agents,
recommendation="Review error logs and consider additional training or constraints"
))
# Pattern 2: Low confidence decisions
low_conf_agents = self._find_low_confidence_agents(days)
if low_conf_agents:
patterns.append(Pattern(
pattern_type="LOW_CONFIDENCE",
description="Agents consistently making low-confidence decisions",
frequency=len(low_conf_agents),
confidence=0.85,
agents_affected=low_conf_agents,
recommendation="Provide clearer instructions or reduce task complexity"
))
# Pattern 3: Action concentration
concentrated_agents = self._find_action_concentration(days)
if concentrated_agents:
patterns.append(Pattern(
pattern_type="ACTION_CONCENTRATION",
description="Agents heavily focused on single action type",
frequency=len(concentrated_agents),
confidence=0.7,
agents_affected=concentrated_agents,
recommendation="Consider diversifying agent responsibilities or creating specialists"
))
# Pattern 4: Success streaks
success_agents = self._find_success_streaks(days)
if success_agents:
patterns.append(Pattern(
pattern_type="SUCCESS_STREAK",
description="Agents with high success streaks (promotion candidates)",
frequency=len(success_agents),
confidence=0.95,
agents_affected=success_agents,
recommendation="Consider promoting these agents to higher tiers"
))
return patterns
def _find_repeated_failures(self, days: int) -> List[str]:
"""Find agents with repeated failures"""
cutoff = (datetime.utcnow() - timedelta(days=days)).isoformat()
query = """
SELECT agent_id, COUNT(*) as fail_count
FROM agent_actions
WHERE success = 0 AND created_at > ?
GROUP BY agent_id
HAVING fail_count >= 3
"""
cursor = self.conn.execute(query, (cutoff,))
return [row["agent_id"] for row in cursor]
def _find_low_confidence_agents(self, days: int) -> List[str]:
"""Find agents with consistently low confidence"""
cutoff = (datetime.utcnow() - timedelta(days=days)).isoformat()
query = """
SELECT agent_id, AVG(confidence) as avg_conf
FROM agent_actions
WHERE created_at > ? AND confidence IS NOT NULL
GROUP BY agent_id
HAVING avg_conf < 0.7 AND COUNT(*) >= 3
"""
cursor = self.conn.execute(query, (cutoff,))
return [row["agent_id"] for row in cursor]
def _find_action_concentration(self, days: int) -> List[str]:
"""Find agents concentrated on single action type"""
stats = self.get_agent_stats(days=days)
concentrated = []
for stat in stats:
if stat.total_actions >= 5:
max_action = max(stat.action_distribution.values()) if stat.action_distribution else 0
if max_action / stat.total_actions > 0.8:
concentrated.append(stat.agent_id)
return concentrated
def _find_success_streaks(self, days: int) -> List[str]:
"""Find agents with high success streaks"""
cutoff = (datetime.utcnow() - timedelta(days=days)).isoformat()
query = """
SELECT agent_id,
SUM(success) as successes,
COUNT(*) as total
FROM agent_actions
WHERE created_at > ?
GROUP BY agent_id
HAVING total >= 5 AND (successes * 1.0 / total) >= 0.9
"""
cursor = self.conn.execute(query, (cutoff,))
return [row["agent_id"] for row in cursor]
def predict_failures(self, days: int = 7) -> List[Prediction]:
"""Predict potential failures based on recent trends"""
predictions = []
stats = self.get_agent_stats(days=days)
for stat in stats:
risk_factors = []
risk_score = 0.0
# Factor 1: Recent failure rate
if stat.total_actions > 0:
failure_rate = stat.failed_actions / stat.total_actions
if failure_rate > 0.3:
risk_factors.append(f"High failure rate: {failure_rate:.1%}")
risk_score += failure_rate * 0.4
# Factor 2: Low average confidence
if stat.avg_confidence < 0.6:
risk_factors.append(f"Low avg confidence: {stat.avg_confidence:.2f}")
risk_score += (1 - stat.avg_confidence) * 0.3
# Factor 3: Recurring error types
if stat.error_types:
recurring = [e for e, c in stat.error_types.items() if c >= 2]
if recurring:
risk_factors.append(f"Recurring errors: {', '.join(recurring)}")
risk_score += 0.2
# Factor 4: Check DragonflyDB for recent revoke signals
revoke_signal = self.redis.get(f"agent:{stat.agent_id}:revoke_signal")
if revoke_signal == "1":
risk_factors.append("Revocation signal active")
risk_score += 0.3
if risk_factors:
risk_level = (
"critical" if risk_score > 0.7 else
"high" if risk_score > 0.5 else
"medium" if risk_score > 0.3 else
"low"
)
recommendations = self._generate_recommendations(stat, risk_factors)
predictions.append(Prediction(
agent_id=stat.agent_id,
risk_level=risk_level,
risk_score=min(risk_score, 1.0),
factors=risk_factors,
recommended_actions=recommendations
))
# Sort by risk score
predictions.sort(key=lambda p: p.risk_score, reverse=True)
return predictions
def _generate_recommendations(self, stat: AgentStats, factors: List[str]) -> List[str]:
"""Generate recommendations based on analysis"""
recommendations = []
if stat.failed_actions > stat.successful_actions:
recommendations.append("Consider reducing task complexity or scope")
if stat.avg_confidence < 0.7:
recommendations.append("Provide more detailed instructions")
if stat.error_types:
most_common = max(stat.error_types, key=stat.error_types.get)
recommendations.append(f"Investigate root cause of '{most_common}' errors")
if stat.promotion_potential < 0.3:
recommendations.append("Agent needs more successful runs before promotion")
if not recommendations:
recommendations.append("Monitor agent closely for next few runs")
return recommendations
def suggest_optimizations(self) -> List[Dict[str, Any]]:
"""Suggest system-wide optimizations"""
suggestions = []
# Get overall stats
query = """
SELECT
COUNT(*) as total_actions,
SUM(success) as successes,
AVG(confidence) as avg_confidence,
COUNT(DISTINCT agent_id) as unique_agents
FROM agent_actions
WHERE created_at > datetime('now', '-30 days')
"""
row = self.conn.execute(query).fetchone()
if row["total_actions"] > 0:
success_rate = row["successes"] / row["total_actions"]
# Suggestion 1: Overall success rate
if success_rate < 0.8:
suggestions.append({
"category": "Success Rate",
"current": f"{success_rate:.1%}",
"target": "80%+",
"suggestion": "Review failing agents and consider additional constraints",
"priority": "high" if success_rate < 0.6 else "medium"
})
# Suggestion 2: Confidence levels
if row["avg_confidence"] and row["avg_confidence"] < 0.75:
suggestions.append({
"category": "Confidence",
"current": f"{row['avg_confidence']:.2f}",
"target": "0.75+",
"suggestion": "Improve task clarity and agent training",
"priority": "medium"
})
# Suggestion 3: Agent utilization
metrics_query = """
SELECT agent_id, total_runs, compliant_runs
FROM agent_metrics
WHERE total_runs > 0
"""
idle_agents = []
for row in self.conn.execute(metrics_query):
if row["total_runs"] < 5:
idle_agents.append(row["agent_id"])
if idle_agents:
suggestions.append({
"category": "Agent Utilization",
"current": f"{len(idle_agents)} underutilized agents",
"target": "All agents active",
"suggestion": f"Consider assigning more tasks to: {', '.join(idle_agents[:3])}",
"priority": "low"
})
# Suggestion 4: Promotion queue
promotable = self._find_success_streaks(30)
if promotable:
suggestions.append({
"category": "Promotions",
"current": f"{len(promotable)} agents ready",
"target": "Process promotion queue",
"suggestion": f"Review for promotion: {', '.join(promotable[:3])}",
"priority": "medium"
})
return suggestions
def generate_report(self) -> Dict[str, Any]:
"""Generate a comprehensive analytics report"""
stats = self.get_agent_stats(days=30)
patterns = self.detect_patterns(days=30)
predictions = self.predict_failures(days=7)
suggestions = self.suggest_optimizations()
# Calculate summaries
total_agents = len(stats)
total_actions = sum(s.total_actions for s in stats)
total_successes = sum(s.successful_actions for s in stats)
avg_confidence = statistics.mean([s.avg_confidence for s in stats]) if stats else 0
return {
"generated_at": datetime.utcnow().isoformat(),
"period_days": 30,
"summary": {
"total_agents": total_agents,
"total_actions": total_actions,
"success_rate": total_successes / total_actions if total_actions > 0 else 0,
"avg_confidence": avg_confidence
},
"patterns_detected": len(patterns),
"patterns": [
{
"type": p.pattern_type,
"description": p.description,
"agents_count": len(p.agents_affected),
"recommendation": p.recommendation
}
for p in patterns
],
"risk_predictions": len([p for p in predictions if p.risk_level in ["high", "critical"]]),
"high_risk_agents": [
{
"agent_id": p.agent_id,
"risk_level": p.risk_level,
"risk_score": p.risk_score,
"top_factor": p.factors[0] if p.factors else "Unknown"
}
for p in predictions[:5]
],
"optimization_suggestions": suggestions,
"top_performers": [
{"agent_id": s.agent_id, "success_rate": s.successful_actions / s.total_actions if s.total_actions > 0 else 0}
for s in sorted(stats, key=lambda x: x.promotion_potential, reverse=True)[:3]
]
}
def main():
"""Run analytics and print report"""
print("=" * 60)
print("AGENT GOVERNANCE ANALYTICS")
print("=" * 60)
analyzer = HistoryAnalyzer()
try:
report = analyzer.generate_report()
print(f"\nPeriod: Last {report['period_days']} days")
print(f"Generated: {report['generated_at']}")
print("\n--- SUMMARY ---")
summary = report["summary"]
print(f" Total Agents: {summary['total_agents']}")
print(f" Total Actions: {summary['total_actions']}")
print(f" Success Rate: {summary['success_rate']:.1%}")
print(f" Avg Confidence: {summary['avg_confidence']:.2f}")
print(f"\n--- PATTERNS DETECTED ({report['patterns_detected']}) ---")
for p in report["patterns"]:
print(f" [{p['type']}] {p['description']}")
print(f" Affects {p['agents_count']} agent(s)")
print(f"{p['recommendation']}")
print(f"\n--- RISK PREDICTIONS ---")
if report["high_risk_agents"]:
for agent in report["high_risk_agents"]:
print(f" {agent['risk_level'].upper()}: {agent['agent_id']} (score: {agent['risk_score']:.2f})")
print(f" Factor: {agent['top_factor']}")
else:
print(" No high-risk agents detected")
print(f"\n--- OPTIMIZATION SUGGESTIONS ---")
for s in report["optimization_suggestions"]:
print(f" [{s['priority'].upper()}] {s['category']}")
print(f" Current: {s['current']} → Target: {s['target']}")
print(f"{s['suggestion']}")
print(f"\n--- TOP PERFORMERS ---")
for p in report["top_performers"]:
print(f" {p['agent_id']}: {p['success_rate']:.1%} success rate")
print("\n" + "=" * 60)
finally:
analyzer.close()
if __name__ == "__main__":
main()

34
bin/README.md Normal file
View File

@ -0,0 +1,34 @@
# Bin
> CLI tools and executable scripts
## Overview
This directory contains cli tools and executable scripts.
## Key Files
| File | Description |
|------|-------------|
| `status.py` | Python module |
| `register-agent.sh` | Shell script |
| `sample-tier0-agent.sh` | Shell script |
## Interfaces / APIs
*Document any APIs, CLI commands, or interfaces here.*
## Status
** Not Started**
See [STATUS.md](./STATUS.md) for detailed progress tracking.
## Architecture Reference
Part of the [Agent Governance System](/opt/agent-governance/docs/ARCHITECTURE.md).
Parent: [Project Root](/opt/agent-governance)
---
*Last updated: 2026-01-23 23:25:09 UTC*

35
bin/STATUS.md Normal file
View File

@ -0,0 +1,35 @@
# Status: Bin
## 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:31 UTC
- **Phase**: COMPLETE
- **Action**: CLI tools: checkpoint, status commands implemented
- **Details**: Phase updated to complete
### 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:31 UTC*

84
bin/checkpoint Executable file
View File

@ -0,0 +1,84 @@
#!/bin/bash
#
# Context Checkpoint Skill for Claude Code
# =========================================
# Preserves session context and orchestrates sub-agent calls.
#
# Skill documentation: /opt/agent-governance/checkpoint/CLAUDE.md
#
# Commands:
# now [--notes "..."] Create checkpoint capturing current state
# load [checkpoint_id] Load latest or specific checkpoint
# diff [--from ID] [--to ID] Compare checkpoints
# list [--limit N] List available checkpoints
# summary --level <level> Context summary (minimal/compact/standard/full)
# report [--phase <phase>] Combined checkpoint + directory status report
# timeline [--limit N] Show checkpoint history with status changes
# auto-orchestrate --model <m> Delegate to OpenRouter models
# queue list|add|clear|pop Manage instruction queue
# prune [--keep N] Remove old checkpoints
#
# Examples:
# checkpoint now --notes "Before deploy"
# checkpoint load --json
# checkpoint summary --level compact
# checkpoint auto-orchestrate --model minimax --instruction "run tests"
#
set -euo pipefail
CHECKPOINT_DIR="/opt/agent-governance/checkpoint"
CHECKPOINT_SCRIPT="${CHECKPOINT_DIR}/checkpoint.py"
# Show help if no args or --help
if [[ $# -eq 0 ]] || [[ "${1:-}" == "--help" ]] || [[ "${1:-}" == "-h" ]]; then
cat << 'EOF'
Context Checkpoint Skill
========================
Commands:
now [--notes "..."] Create checkpoint (includes directory statuses)
load [checkpoint_id] [--json] Load checkpoint
diff [--from ID] [--to ID] Compare checkpoints
list [--limit N] [--json] List checkpoints
summary --level <level> Context summary
report [--phase <phase>] Combined checkpoint + directory status report
timeline [--limit N] Checkpoint history with status changes
auto-orchestrate --model <m> Delegate to AI models
queue list|add|clear|pop Manage instruction queue
prune [--keep N] Remove old checkpoints
Summary Levels:
minimal (~500 tokens) Phase + agent only
compact (~1000 tokens) + tasks + key variables
standard (~2000 tokens) + all tasks + dependencies
full (~4000 tokens) Complete context
Models for auto-orchestrate:
minimax Minimax-01 (default, 100K context)
gemini Gemini 2.0 Flash Thinking (free)
gemini-pro Gemini 2.5 Pro (highest capability)
Examples:
checkpoint now --notes "Phase 5 complete"
checkpoint load --json | jq .phase
checkpoint summary --level compact
checkpoint report # Combined status view
checkpoint report --phase in_progress # Filter by phase
checkpoint timeline --limit 5 # Recent history
checkpoint auto-orchestrate --model minimax -i "check status"
checkpoint queue add --instruction "run tests" --priority 5
Documentation: /opt/agent-governance/checkpoint/CLAUDE.md
EOF
exit 0
fi
# Verify checkpoint script exists
if [[ ! -f "${CHECKPOINT_SCRIPT}" ]]; then
echo "Error: checkpoint.py not found at ${CHECKPOINT_SCRIPT}"
exit 1
fi
# Execute checkpoint command
exec python3 "${CHECKPOINT_SCRIPT}" "$@"

89
bin/ledger-api Executable file
View File

@ -0,0 +1,89 @@
#!/bin/bash
#
# Ledger API Service
# ==================
# Starts the FastAPI ledger service
#
# Usage:
# ledger-api Start on default port (8080)
# ledger-api --port 8081 Start on custom port
# ledger-api --no-auth Disable authentication
#
set -euo pipefail
LEDGER_DIR="/opt/agent-governance/ledger"
VENV_DIR="${LEDGER_DIR}/.venv"
# Parse arguments
PORT="${LEDGER_API_PORT:-8080}"
AUTH="true"
while [[ $# -gt 0 ]]; do
case $1 in
--port)
PORT="$2"
shift 2
;;
--no-auth)
AUTH="false"
shift
;;
--help|-h)
cat << 'EOF'
Ledger API Service
==================
Usage:
ledger-api Start on default port (8080)
ledger-api --port 8081 Start on custom port
ledger-api --no-auth Disable Vault authentication
Environment Variables:
LEDGER_API_PORT Port to listen on (default: 8080)
LEDGER_API_AUTH Require Vault auth (default: true)
VAULT_ADDR Vault address (default: https://127.0.0.1:8200)
Endpoints:
GET /health Health check
GET /docs Swagger documentation
GET /agents List agents
GET /violations List violations
GET /promotions List promotions
GET /stats Overall statistics
EOF
exit 0
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
# Create venv if needed
if [[ ! -d "${VENV_DIR}" ]]; then
echo "Creating virtual environment..."
python3 -m venv "${VENV_DIR}" 2>/dev/null || {
echo "Installing dependencies globally..."
pip3 install -q fastapi uvicorn pydantic
}
fi
# Activate venv if it exists
if [[ -d "${VENV_DIR}" ]]; then
source "${VENV_DIR}/bin/activate"
# Install dependencies if needed
if ! python3 -c "import fastapi" 2>/dev/null; then
echo "Installing dependencies..."
pip install -q -r "${LEDGER_DIR}/requirements.txt"
fi
fi
# Export config
export LEDGER_API_PORT="${PORT}"
export LEDGER_API_AUTH="${AUTH}"
# Run API
exec python3 "${LEDGER_DIR}/api.py"

5
bin/llm-agent Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
# LLM Agent Launcher
export PATH="$HOME/.local/bin:$HOME/.bun/bin:$PATH"
cd /opt/agent-governance/agents/llm-planner
uv run agent.py "$@"

106
bin/memory Executable file
View File

@ -0,0 +1,106 @@
#!/bin/bash
#
# External Memory Layer CLI for Claude Code
# ==========================================
# Token-efficient storage and retrieval of large outputs, transcripts, and context.
#
# Commands:
# log <content> Store content in memory (auto-chunks large content)
# log --file <path> Store file contents
# log --stdin Read from stdin
# fetch <id> Retrieve memory entry
# fetch <id> --summary-only Get just the summary
# fetch <id> --chunk N Get specific chunk
# list [--type TYPE] List memory entries
# search <query> Search memory by content/tags
# summarize <id> Generate/show summary
# refs --checkpoint <id> Get memory refs for checkpoint
# prune Clean old entries
# stats Show memory statistics
#
# Examples:
# echo "Large output..." | memory log --stdin --tag "test"
# memory fetch mem-20260123-123456-abcd --summary-only
# memory list --type output --limit 10
# memory search "error"
#
# Token Guidelines:
# - Content < 500 tokens: stored inline
# - Content 500-4000 tokens: stored in file with summary
# - Content > 4000 tokens: auto-chunked with parent summary
#
# Integration:
# - Link to checkpoints: --checkpoint <id>
# - Link to directories: --directory <path>
# - Summaries auto-generated for efficient retrieval
#
# Documentation: /opt/agent-governance/docs/MEMORY_LAYER.md
set -euo pipefail
MEMORY_SCRIPT="/opt/agent-governance/memory/memory.py"
# Show help if no args or --help
if [[ $# -eq 0 ]] || [[ "${1:-}" == "--help" ]] || [[ "${1:-}" == "-h" ]]; then
cat << 'EOF'
External Memory Layer
=====================
Commands:
log <content> Store content in memory
log --file <path> Store file contents
log --stdin Read from stdin
fetch <id> Retrieve memory entry
fetch <id> -s Summary only
fetch <id> --chunk N Get specific chunk
list [--type TYPE] List entries
search <query> Search memory
summarize <id> Generate summary
refs --checkpoint <id> Memory refs for checkpoint
prune Clean old entries
stats Show statistics
Options for 'log':
--file, -f <path> Read from file
--stdin Read from stdin
--type, -t <type> Type: transcript, output, context
--tag <tag> Add tag (repeatable)
--checkpoint <id> Link to checkpoint
--directory, -d <path> Link to directory
--no-chunk Don't auto-chunk large content
--json Output JSON
Token Thresholds:
< 500 tokens → stored inline
500-4000 tokens → stored in file + summary
> 4000 tokens → auto-chunked + summary
Examples:
# Store large test output
pytest tests/ 2>&1 | memory log --stdin --tag "pytest" --tag "tests"
# Fetch just the summary
memory fetch mem-20260123-123456-abcd -s
# Get chunk 2 of a large entry
memory fetch mem-20260123-123456-abcd --chunk 2
# List recent outputs
memory list --type output --limit 5
# Search for errors
memory search "failed" --limit 10
Documentation: /opt/agent-governance/docs/MEMORY_LAYER.md
EOF
exit 0
fi
# Verify memory script exists
if [[ ! -f "${MEMORY_SCRIPT}" ]]; then
echo "Error: memory.py not found at ${MEMORY_SCRIPT}"
exit 1
fi
# Execute memory command
exec python3 "${MEMORY_SCRIPT}" "$@"

9
bin/model-controller Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
#
# Model Controller CLI
# Wrapper for automated orchestration management
#
SCRIPT_DIR="/opt/agent-governance/orchestrator"
python3 "${SCRIPT_DIR}/model_controller.py" "$@"

65
bin/oversight Executable file
View File

@ -0,0 +1,65 @@
#!/bin/bash
#
# Architectural Test Pipeline CLI
# ================================
# Multi-layer oversight system for continuous validation.
#
# Usage:
# oversight run Full pipeline execution
# oversight run --inject With injection tests
# oversight quick Quick validation
# oversight validate --phase 5 Validate specific phase
# oversight report Generate report
# oversight matrix Show phase status matrix
#
set -euo pipefail
OVERSIGHT_DIR="/opt/agent-governance/testing/oversight"
# Show help if no args or --help
if [[ $# -eq 0 ]] || [[ "${1:-}" == "--help" ]] || [[ "${1:-}" == "-h" ]]; then
cat << 'EOF'
Architectural Test Pipeline
===========================
Multi-layer oversight system for continuous validation across all 12 phases.
Commands:
run [options] Run full pipeline
quick Quick validation (phases + anomalies only)
validate --phase N Validate specific phase in detail
report [--inject] Generate comprehensive report
matrix Show phase status matrix
status Show pipeline status
Options:
--phase N Focus on specific phase
--inject Run injection tests
--unsafe Disable safe mode (caution!)
--auto-fix Enable auto-fix for approved changes
--verbose, -v Verbose output
--json Output as JSON
Layers:
1. Bug Window Watcher Real-time anomaly detection
2. Suggestion Engine AI-driven fix recommendations
3. Council Review Multi-agent decision making
4. Phase Validator Coverage across all phases
5. Error Injector Controlled fault injection
6. Reporter Comprehensive reporting
Examples:
oversight run # Full pipeline
oversight run --inject --verbose # With injection tests
oversight validate --phase 5 # Focus on Phase 5
oversight matrix # Quick status overview
Documentation: /opt/agent-governance/testing/oversight/README.md
EOF
exit 0
fi
# Execute pipeline
cd "${OVERSIGHT_DIR}"
exec python3 -m testing.oversight.pipeline "$@"

1
bin/pipeline Symbolic link
View File

@ -0,0 +1 @@
/opt/agent-governance/pipeline/pipeline.py

148
bin/register-agent.sh Executable file
View File

@ -0,0 +1,148 @@
#!/bin/bash
# Agent Registration Script
# Validates and registers a new agent in Vault
set -e
VAULT_ADDR="${VAULT_ADDR:-https://127.0.0.1:8200}"
export VAULT_SKIP_VERIFY=true
usage() {
echo "Usage: $0 -i <agent_id> -r <role> -t <tier> -o <owner> -v <version>"
echo ""
echo "Options:"
echo " -i Agent ID (lowercase, alphanumeric with dashes)"
echo " -r Role: observer|operator|builder|executor|architect"
echo " -t Tier: 0-4"
echo " -o Owner (human email or 'system')"
echo " -v Version (semver: x.y.z)"
echo ""
echo "Environment:"
echo " VAULT_TOKEN Required for registration"
exit 1
}
while getopts "i:r:t:o:v:h" opt; do
case $opt in
i) AGENT_ID="$OPTARG" ;;
r) ROLE="$OPTARG" ;;
t) TIER="$OPTARG" ;;
o) OWNER="$OPTARG" ;;
v) VERSION="$OPTARG" ;;
h) usage ;;
*) usage ;;
esac
done
# Validate required params
[[ -z "$AGENT_ID" || -z "$ROLE" || -z "$TIER" || -z "$OWNER" || -z "$VERSION" ]] && usage
[[ -z "$VAULT_TOKEN" ]] && echo "Error: VAULT_TOKEN not set" && exit 1
# Validate agent_id format
if [[ ! "$AGENT_ID" =~ ^[a-z0-9-]+$ ]]; then
echo "Error: agent_id must be lowercase alphanumeric with dashes"
exit 1
fi
# Validate role
VALID_ROLES="observer operator builder executor architect"
if [[ ! " $VALID_ROLES " =~ " $ROLE " ]]; then
echo "Error: role must be one of: $VALID_ROLES"
exit 1
fi
# Validate tier
if [[ ! "$TIER" =~ ^[0-4]$ ]]; then
echo "Error: tier must be 0-4"
exit 1
fi
# Validate version (semver)
if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "Error: version must be semver (x.y.z)"
exit 1
fi
# Map role to tier and validate consistency
declare -A ROLE_TIER_MAP=(
["observer"]=0
["operator"]=1
["builder"]=2
["executor"]=3
["architect"]=4
)
EXPECTED_TIER="${ROLE_TIER_MAP[$ROLE]}"
if [[ "$TIER" -ne "$EXPECTED_TIER" ]]; then
echo "Warning: role '$ROLE' typically maps to tier $EXPECTED_TIER, but tier $TIER was specified"
fi
# Define allowed/forbidden actions based on tier
case $TIER in
0)
ALLOWED='["read_docs","read_inventory","read_logs","generate_plan"]'
FORBIDDEN='["ssh","create_vm","modify_vm","delete_vm","run_ansible","run_terraform","write_secrets","execute_shell"]'
;;
1)
ALLOWED='["read_docs","read_inventory","read_logs","generate_plan","ssh_sandbox","create_vm_sandbox","run_ansible_sandbox","run_terraform_sandbox"]'
FORBIDDEN='["ssh_prod","ssh_staging","create_vm_prod","create_vm_staging","run_ansible_prod","run_terraform_prod","write_secrets","modify_baseline"]'
;;
2)
ALLOWED='["read_docs","read_inventory","read_logs","generate_plan","ssh_sandbox","create_vm_sandbox","run_ansible_sandbox","run_terraform_sandbox","modify_frameworks","create_templates"]'
FORBIDDEN='["ssh_prod","create_vm_prod","run_ansible_prod","run_terraform_prod","modify_blessed_baseline","direct_prod_access"]'
;;
3)
ALLOWED='["read_docs","read_inventory","read_logs","generate_plan","ssh_sandbox","ssh_staging","ssh_prod_controlled","create_vm_sandbox","create_vm_staging","run_ansible_all","run_terraform_all"]'
FORBIDDEN='["unbounded_root","wide_scope_apply","skip_recording","modify_governance"]'
;;
4)
ALLOWED='["read_all","propose_policy","propose_baseline","request_blessing","emergency_response"]'
FORBIDDEN='["self_approve","self_escalate","bypass_audit"]'
;;
esac
# Set TTL based on tier (higher tier = shorter TTL)
TTL_MAP=(3600 1800 1800 900 900)
TTL=${TTL_MAP[$TIER]}
# Confidence threshold (higher tier = higher threshold required)
CONF_MAP=(0.7 0.75 0.8 0.85 0.9)
CONFIDENCE=${CONF_MAP[$TIER]}
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
echo "Registering agent: $AGENT_ID"
echo " Role: $ROLE (Tier $TIER)"
echo " Owner: $OWNER"
echo " Version: $VERSION"
echo " TTL: ${TTL}s"
echo " Confidence threshold: $CONFIDENCE"
# Register in Vault
docker exec -e VAULT_TOKEN="$VAULT_TOKEN" -e VAULT_ADDR="$VAULT_ADDR" vault \
vault kv put "secret/agents/$AGENT_ID" \
agent_id="$AGENT_ID" \
agent_role="$ROLE" \
owner="$OWNER" \
version="$VERSION" \
tier="$TIER" \
input_contract="secret/docs/schemas/task-request" \
output_contract="secret/docs/schemas/agent-output" \
allowed_side_effects="$ALLOWED" \
forbidden_actions="$FORBIDDEN" \
confidence_reporting=true \
confidence_threshold="$CONFIDENCE" \
ttl_seconds="$TTL" \
status="registered" \
created_at="$TIMESTAMP" \
last_active="$TIMESTAMP" \
compliant_runs=0 \
consecutive_compliant=0 \
violations=0
echo ""
echo "Agent registered successfully."
echo ""
echo "To generate credentials for this agent:"
echo " vault read auth/approle/role/tier${TIER}-agent/role-id"
echo " vault write -f auth/approle/role/tier${TIER}-agent/secret-id"

99
bin/sample-tier0-agent.sh Executable file
View File

@ -0,0 +1,99 @@
#!/bin/bash
# Sample Tier 0 Agent: Plan Generator
# Demonstrates proper agent behavior per foundation document
set -e
# Load bootstrap library
source /opt/agent-governance/lib/agent-bootstrap.sh
AGENT_ID="plan-generator-001"
# --- Agent Metadata Declaration (Section 4) ---
declare -A AGENT_META=(
[agent_id]="plan-generator-001"
[agent_role]="observer"
[version]="0.1.0"
[tier]=0
)
main() {
log_info "Starting agent: ${AGENT_META[agent_id]} v${AGENT_META[version]}"
# Check for required credentials
if [[ -z "$ROLE_ID" || -z "$SECRET_ID" ]]; then
agent_error "CONFIGURATION_ERROR" \
"Missing credentials" \
"Environment variables ROLE_ID and SECRET_ID" \
"None" \
"Set ROLE_ID and SECRET_ID environment variables"
exit 1
fi
# Authenticate (Section 3.3 - Bounded Authority)
if ! agent_authenticate "$ROLE_ID" "$SECRET_ID"; then
agent_error "AUTH_ERROR" \
"Failed to authenticate with Vault" \
"role_id=$ROLE_ID" \
"None" \
"Verify credentials and Vault connectivity"
exit 1
fi
# Load metadata from Vault
if ! agent_load_metadata "$AGENT_ID"; then
agent_error "METADATA_ERROR" \
"Failed to load agent metadata" \
"agent_id=$AGENT_ID" \
"Authenticated successfully" \
"Verify agent is registered in Vault"
exit 1
fi
# Validate action before proceeding (Section 5 - Input Discipline)
local requested_action="${1:-read_docs}"
if ! agent_validate_action "$requested_action"; then
agent_error "FORBIDDEN_ACTION" \
"Requested action is not permitted for this agent" \
"action=$requested_action" \
"Authenticated and loaded metadata" \
"Request action within allowed scope or escalate to higher tier"
exit 1
fi
# Execute action
case "$requested_action" in
read_docs)
log_info "Reading documentation..."
local docs
docs=$(curl -sk -H "X-Vault-Token: $VAULT_TOKEN" \
"$VAULT_ADDR/v1/secret/data/docs/agent-taxonomy" | jq -r '.data.data')
if [[ -n "$docs" ]]; then
agent_output "EXECUTE" 0.95 "read_docs" "Successfully read agent taxonomy documentation"
else
agent_output "ERROR" 0.0 "read_docs" "Failed to read documentation"
fi
;;
read_inventory)
log_info "Reading inventory..."
local inventory
inventory=$(curl -sk -H "X-Vault-Token: $VAULT_TOKEN" \
"$VAULT_ADDR/v1/secret/data/inventory/proxmox" | jq -r '.data.data')
agent_output "EXECUTE" 0.90 "read_inventory" "Read Proxmox inventory: $(echo "$inventory" | jq -r '.cluster')"
;;
generate_plan)
log_info "Generating plan..."
# Tier 0 can generate plans but not execute
agent_output "EXECUTE" 0.85 "generate_plan" "Plan generated. Requires Tier 1+ agent for execution."
;;
*)
agent_output "INSUFFICIENT_INFORMATION" 0.0 "$requested_action" "Unknown action requested"
;;
esac
}
# Run main
main "$@"

69
bin/status Executable file
View File

@ -0,0 +1,69 @@
#!/bin/bash
#
# Directory Status Management Skill for Claude Code
# ==================================================
# Manages README.md and STATUS.md files across all subdirectories.
#
# Commands:
# sweep [--fix] Check all directories for README/STATUS files
# update <dir> [options] Update status for a directory
# init <dir> Initialize README/STATUS in a directory
# dashboard Show status overview of all directories
# template [readme|status] Show file templates
#
# Examples:
# status sweep Check all directories
# status sweep --fix Auto-create missing files
# status update ./pipeline --phase "complete" --task "Pipeline unified"
# status dashboard Show project-wide status
set -euo pipefail
STATUS_SCRIPT="/opt/agent-governance/bin/status.py"
# Show help if no args or --help
if [[ $# -eq 0 ]] || [[ "${1:-}" == "--help" ]] || [[ "${1:-}" == "-h" ]]; then
cat << 'EOF'
Directory Status Management
===========================
Commands:
sweep [--fix] Check all directories for README/STATUS
update <dir> [options] Update directory status
init <dir> Initialize README/STATUS files
dashboard Show status overview
template [readme|status] Show file templates
Update Options:
--phase <phase> Set phase (in_progress/complete/blocked/needs_review)
--task <description> Add task entry
--note <note> Add note to status log
--deps <dependencies> Set dependencies
Status Indicators:
[COMPLETE] Directory work is finished
[IN_PROGRESS] Active development
[BLOCKED] Waiting on dependencies
[NEEDS_REVIEW] Requires attention
Examples:
status sweep
status sweep --fix
status update ./pipeline --phase complete
status update ./tests --task "Add chaos tests" --phase in_progress
status dashboard
status init ./new-module
Documentation: /opt/agent-governance/docs/STATUS_PROTOCOL.md
EOF
exit 0
fi
# Verify status script exists
if [[ ! -f "${STATUS_SCRIPT}" ]]; then
echo "Error: status.py not found at ${STATUS_SCRIPT}"
exit 1
fi
# Execute status command
exec python3 "${STATUS_SCRIPT}" "$@"

849
bin/status.py Executable file
View File

@ -0,0 +1,849 @@
#!/usr/bin/env python3
"""
Directory Status Management System
===================================
Manages README.md and STATUS.md files across the agent-governance project.
Ensures every subdirectory has proper documentation and status tracking.
Usage:
python status.py sweep [--fix]
python status.py update <dir> --phase <phase> [--task <task>]
python status.py init <dir>
python status.py dashboard
python status.py template [readme|status]
"""
import os
import sys
import json
import argparse
from datetime import datetime, timezone
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass, field
from enum import Enum
# Project root
PROJECT_ROOT = Path("/opt/agent-governance")
# Directories to skip (data/generated content)
SKIP_DIRS = {
"__pycache__",
"node_modules",
".git",
"logs",
"storage",
"dragonfly-data",
"credentials",
"workspace",
".claude",
}
# Directories that are data-only (no README needed)
DATA_DIRS = {
"logs",
"storage",
"dragonfly-data",
"credentials",
"workspace",
"packages", # evidence packages are generated
}
class StatusPhase(str, Enum):
"""Status phases for directories."""
COMPLETE = "complete"
IN_PROGRESS = "in_progress"
BLOCKED = "blocked"
NEEDS_REVIEW = "needs_review"
NOT_STARTED = "not_started"
STATUS_ICONS = {
StatusPhase.COMPLETE: "",
StatusPhase.IN_PROGRESS: "",
StatusPhase.BLOCKED: "",
StatusPhase.NEEDS_REVIEW: "",
StatusPhase.NOT_STARTED: "",
}
@dataclass
class DirectoryStatus:
"""Status information for a directory."""
path: Path
has_readme: bool = False
has_status: bool = False
phase: StatusPhase = StatusPhase.NOT_STARTED
last_updated: Optional[datetime] = None
tasks: List[Dict] = field(default_factory=list)
dependencies: List[str] = field(default_factory=list)
issues: List[str] = field(default_factory=list)
# =============================================================================
# Templates
# =============================================================================
README_TEMPLATE = '''# {title}
> {purpose}
## Overview
{overview}
## Key Files
| File | Description |
|------|-------------|
{files_table}
## Interfaces / APIs
{interfaces}
## Status
{status_badge}
See [STATUS.md](./STATUS.md) for detailed progress tracking.
## Architecture Reference
Part of the [Agent Governance System](/opt/agent-governance/docs/ARCHITECTURE.md).
Parent: [{parent_name}]({parent_path})
---
*Last updated: {timestamp}*
'''
STATUS_TEMPLATE = '''# Status: {title}
## Current Phase
{phase_badge}
## Tasks
| Status | Task | Updated |
|--------|------|---------|
{tasks_table}
## Dependencies
{dependencies}
## Issues / Blockers
{issues}
## Activity Log
{activity_log}
---
*Last updated: {timestamp}*
'''
ACTIVITY_ENTRY_TEMPLATE = '''### {timestamp}
- **Phase**: {phase}
- **Action**: {action}
- **Details**: {details}
'''
def get_readme_template() -> str:
return README_TEMPLATE
def get_status_template() -> str:
return STATUS_TEMPLATE
# =============================================================================
# Directory Analysis
# =============================================================================
def should_skip_dir(dir_path: Path) -> bool:
"""Check if directory should be skipped."""
name = dir_path.name
if name.startswith("."):
return True
if name in SKIP_DIRS:
return True
# Skip evidence package subdirs (generated)
if "evd-" in name:
return True
return False
def is_data_dir(dir_path: Path) -> bool:
"""Check if directory is data-only."""
return dir_path.name in DATA_DIRS
def get_all_directories() -> List[Path]:
"""Get all directories that should have README/STATUS."""
dirs = []
for root, subdirs, files in os.walk(PROJECT_ROOT):
root_path = Path(root)
# Filter subdirs in-place to skip certain directories
subdirs[:] = [d for d in subdirs if not should_skip_dir(root_path / d)]
# Skip data directories
if is_data_dir(root_path):
continue
dirs.append(root_path)
return sorted(dirs)
def analyze_directory(dir_path: Path) -> DirectoryStatus:
"""Analyze a directory's status files."""
status = DirectoryStatus(path=dir_path)
readme_path = dir_path / "README.md"
status_path = dir_path / "STATUS.md"
alt_status_path = dir_path / "README.status.md"
status.has_readme = readme_path.exists()
status.has_status = status_path.exists() or alt_status_path.exists()
# Parse STATUS.md if exists
actual_status_path = status_path if status_path.exists() else alt_status_path
if actual_status_path.exists():
try:
content = actual_status_path.read_text()
status.phase = parse_phase_from_status(content)
status.last_updated = parse_timestamp_from_status(content)
status.tasks = parse_tasks_from_status(content)
status.dependencies = parse_dependencies_from_status(content)
status.issues = parse_issues_from_status(content)
except Exception:
pass
return status
def parse_phase_from_status(content: str) -> StatusPhase:
"""Extract phase from STATUS.md content."""
content_lower = content.lower()
if "complete" in content_lower and ("phase" in content_lower or "status" in content_lower):
if "in_progress" not in content_lower and "in progress" not in content_lower:
return StatusPhase.COMPLETE
if "in_progress" in content_lower or "in progress" in content_lower:
return StatusPhase.IN_PROGRESS
if "blocked" in content_lower:
return StatusPhase.BLOCKED
if "needs_review" in content_lower or "needs review" in content_lower:
return StatusPhase.NEEDS_REVIEW
return StatusPhase.NOT_STARTED
def parse_timestamp_from_status(content: str) -> Optional[datetime]:
"""Extract last updated timestamp from STATUS.md."""
import re
# Look for ISO format timestamps
pattern = r'(\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2})'
matches = re.findall(pattern, content)
if matches:
try:
ts = matches[-1].replace(" ", "T")
return datetime.fromisoformat(ts)
except:
pass
return None
def parse_tasks_from_status(content: str) -> List[Dict]:
"""Extract tasks from STATUS.md."""
tasks = []
import re
# Look for task table rows
pattern = r'\|\s*([x\- ✓✗])\s*\|\s*([^|]+)\|'
for match in re.finditer(pattern, content, re.IGNORECASE):
status_char = match.group(1).strip()
task_text = match.group(2).strip()
if task_text and task_text != "Task": # Skip header
done = status_char.lower() in ['x', '', 'done', 'complete']
tasks.append({"task": task_text, "done": done})
return tasks
def parse_dependencies_from_status(content: str) -> List[str]:
"""Extract dependencies from STATUS.md."""
deps = []
in_deps_section = False
for line in content.split('\n'):
if '## Dependencies' in line or '## Depends' in line:
in_deps_section = True
continue
if in_deps_section:
if line.startswith('##'):
break
if line.strip().startswith('-'):
deps.append(line.strip()[1:].strip())
return deps
def parse_issues_from_status(content: str) -> List[str]:
"""Extract issues/blockers from STATUS.md."""
issues = []
in_issues_section = False
for line in content.split('\n'):
if '## Issues' in line or '## Blockers' in line:
in_issues_section = True
continue
if in_issues_section:
if line.startswith('##'):
break
if line.strip().startswith('-'):
issues.append(line.strip()[1:].strip())
return issues
# =============================================================================
# File Generation
# =============================================================================
def get_directory_purpose(dir_path: Path) -> str:
"""Get a purpose description for a directory based on its name/location."""
purposes = {
"agents": "Agent implementations and configurations",
"analytics": "Learning analytics and pattern detection",
"bin": "CLI tools and executable scripts",
"checkpoint": "Context checkpoint and session management",
"docs": "System documentation and architecture specs",
"evidence": "Audit evidence and execution artifacts",
"integrations": "External service integrations (GitHub, Slack)",
"inventory": "Infrastructure inventory management",
"ledger": "SQLite audit ledger and transaction logs",
"lib": "Shared library code and utilities",
"orchestrator": "Multi-agent orchestration system",
"pipeline": "Pipeline DSL, schemas, and templates",
"preflight": "Pre-execution validation and checks",
"runtime": "Runtime governance and agent lifecycle",
"sandbox": "Sandboxed execution environments",
"schemas": "JSON schemas for validation",
"teams": "Hierarchical team framework",
"testing": "Test utilities and helpers",
"tests": "Test suites and test infrastructure",
"ui": "User interface components",
"wrappers": "Tool wrappers for sandboxed execution",
"tier0-agent": "Observer-tier agent (read-only)",
"tier1-agent": "Executor-tier agent (infrastructure changes)",
"multi-agent": "Multi-agent coordination demos",
"llm-planner": "LLM-based planning agent (Python)",
"llm-planner-ts": "LLM-based planning agent (TypeScript)",
"chaos": "Chaos testing framework",
"multi-agent-chaos": "Multi-agent chaos test suite",
"github": "GitHub integration module",
"slack": "Slack integration module",
"common": "Common integration utilities",
"framework": "Team framework core implementation",
"templates": "Configuration and pipeline templates",
"examples": "Example configurations and demos",
"schemas": "JSON schema definitions",
"mocks": "Mock implementations for testing",
"unit": "Unit tests",
"integration": "Integration tests",
"scenarios": "Test scenarios",
"governance": "Governance system tests",
"terraform": "Terraform sandbox and examples",
"ansible": "Ansible sandbox and examples",
}
name = dir_path.name
if name in purposes:
return purposes[name]
# Check parent context
parent = dir_path.parent.name
if parent in purposes:
return f"{purposes[parent]} - {name} submodule"
return f"Component of the Agent Governance System"
def get_key_files(dir_path: Path) -> List[Tuple[str, str]]:
"""Get key files in a directory with descriptions."""
files = []
# Prioritize certain file types
priority_patterns = [
("*.py", "Python module"),
("*.ts", "TypeScript module"),
("*.js", "JavaScript module"),
("*.yaml", "Configuration"),
("*.yml", "Configuration"),
("*.json", "Data/Schema"),
("*.sh", "Shell script"),
("*.sql", "Database schema"),
("*.md", "Documentation"),
]
seen = set()
for pattern, desc in priority_patterns:
for f in dir_path.glob(pattern):
if f.name not in seen and not f.name.startswith('.'):
if f.name not in ('README.md', 'STATUS.md', 'README.status.md'):
files.append((f.name, f"{desc}"))
seen.add(f.name)
return files[:10] # Limit to top 10
def generate_readme(dir_path: Path, phase: StatusPhase = StatusPhase.NOT_STARTED) -> str:
"""Generate README.md content for a directory."""
name = dir_path.name
title = name.replace("-", " ").replace("_", " ").title()
purpose = get_directory_purpose(dir_path)
# Get key files
key_files = get_key_files(dir_path)
if key_files:
files_table = "\n".join(f"| `{f}` | {d} |" for f, d in key_files)
else:
files_table = "| *No files yet* | |"
# Get parent info
parent = dir_path.parent
if parent == PROJECT_ROOT:
parent_name = "Project Root"
parent_path = "/opt/agent-governance"
else:
parent_name = parent.name.replace("-", " ").title()
parent_path = f".."
# Status badge
icon = STATUS_ICONS.get(phase, "")
phase_label = phase.value.replace("_", " ").title()
status_badge = f"**{icon} {phase_label}**"
# Overview - try to be smart about it
overview = f"This directory contains {purpose.lower()}."
# Interfaces placeholder
interfaces = "*Document any APIs, CLI commands, or interfaces here.*"
return README_TEMPLATE.format(
title=title,
purpose=purpose,
overview=overview,
files_table=files_table,
interfaces=interfaces,
status_badge=status_badge,
parent_name=parent_name,
parent_path=parent_path,
timestamp=datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC"),
)
def generate_status(dir_path: Path, phase: StatusPhase = StatusPhase.NOT_STARTED,
tasks: List[Dict] = None, dependencies: List[str] = None,
issues: List[str] = None, action: str = "Initialized") -> str:
"""Generate STATUS.md content for a directory."""
name = dir_path.name
title = name.replace("-", " ").replace("_", " ").title()
# Phase badge
icon = STATUS_ICONS.get(phase, "")
phase_label = phase.value.replace("_", " ").upper()
phase_badge = f"**{icon} {phase_label}**"
# Tasks table
tasks = tasks or []
if tasks:
tasks_table = "\n".join(
f"| {'' if t.get('done') else ''} | {t['task']} | {t.get('updated', 'N/A')} |"
for t in tasks
)
else:
tasks_table = "| ☐ | *No tasks defined* | - |"
# Dependencies
dependencies = dependencies or []
if dependencies:
deps_text = "\n".join(f"- {d}" for d in dependencies)
else:
deps_text = "*No external dependencies.*"
# Issues
issues = issues or []
if issues:
issues_text = "\n".join(f"- {i}" for i in issues)
else:
issues_text = "*No current issues or blockers.*"
# Activity log
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
activity_log = ACTIVITY_ENTRY_TEMPLATE.format(
timestamp=timestamp,
phase=phase_label,
action=action,
details="Status tracking initialized for this directory."
)
return STATUS_TEMPLATE.format(
title=title,
phase_badge=phase_badge,
tasks_table=tasks_table,
dependencies=deps_text,
issues=issues_text,
activity_log=activity_log,
timestamp=timestamp,
)
# =============================================================================
# Commands
# =============================================================================
def cmd_sweep(args):
"""Check all directories for README/STATUS files."""
dirs = get_all_directories()
missing_readme = []
missing_status = []
outdated = []
print("=" * 60)
print("DIRECTORY STATUS SWEEP")
print("=" * 60)
print(f"Scanning {len(dirs)} directories...\n")
for dir_path in dirs:
status = analyze_directory(dir_path)
rel_path = dir_path.relative_to(PROJECT_ROOT)
issues = []
if not status.has_readme:
issues.append("missing README.md")
missing_readme.append(dir_path)
if not status.has_status:
issues.append("missing STATUS.md")
missing_status.append(dir_path)
if status.last_updated:
age = datetime.now(timezone.utc) - status.last_updated.replace(tzinfo=timezone.utc)
if age.days > 7:
issues.append(f"outdated ({age.days} days)")
outdated.append(dir_path)
if issues:
print(f" {rel_path}: {', '.join(issues)}")
print()
print("-" * 60)
print(f"Missing README.md: {len(missing_readme)}")
print(f"Missing STATUS.md: {len(missing_status)}")
print(f"Outdated (>7 days): {len(outdated)}")
print("-" * 60)
if args.fix and (missing_readme or missing_status):
print("\nFixing missing files...")
for dir_path in set(missing_readme + missing_status):
rel_path = dir_path.relative_to(PROJECT_ROOT)
readme_path = dir_path / "README.md"
status_path = dir_path / "STATUS.md"
if not readme_path.exists():
readme_path.write_text(generate_readme(dir_path))
print(f" Created: {rel_path}/README.md")
if not status_path.exists():
status_path.write_text(generate_status(dir_path))
print(f" Created: {rel_path}/STATUS.md")
print("\nDone!")
elif missing_readme or missing_status:
print("\nRun with --fix to create missing files.")
return 0
def cmd_update(args):
"""Update status for a directory."""
dir_path = Path(args.directory).resolve()
if not dir_path.exists():
print(f"Error: Directory not found: {dir_path}")
return 1
status_path = dir_path / "STATUS.md"
# Parse phase
phase = StatusPhase.IN_PROGRESS
if args.phase:
try:
phase = StatusPhase(args.phase.lower().replace(" ", "_"))
except ValueError:
print(f"Error: Invalid phase '{args.phase}'")
print(f"Valid phases: {', '.join(p.value for p in StatusPhase)}")
return 1
# Read existing or create new
if status_path.exists():
content = status_path.read_text()
# Update phase in existing content
timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
# Add new activity log entry
new_entry = ACTIVITY_ENTRY_TEMPLATE.format(
timestamp=timestamp,
phase=phase.value.upper(),
action=args.task or args.note or "Status update",
details=args.note or f"Phase updated to {phase.value}",
)
# Update phase badge
icon = STATUS_ICONS.get(phase, "")
phase_label = phase.value.replace("_", " ").upper()
new_badge = f"**{icon} {phase_label}**"
import re
content = re.sub(r'\*\*[✅🚧❗⚠️⬜] [A-Z_]+\*\*', new_badge, content)
# Insert new activity entry
if "## Activity Log" in content:
content = content.replace(
"## Activity Log\n",
f"## Activity Log\n\n{new_entry}"
)
# Update timestamp
content = re.sub(
r'\*Last updated: [^*]+\*',
f'*Last updated: {timestamp}*',
content
)
status_path.write_text(content)
else:
# Create new STATUS.md
content = generate_status(
dir_path,
phase=phase,
action=args.task or "Initialized",
)
status_path.write_text(content)
rel_path = dir_path.relative_to(PROJECT_ROOT) if str(dir_path).startswith(str(PROJECT_ROOT)) else dir_path
print(f"Updated: {rel_path}/STATUS.md")
print(f" Phase: {phase.value}")
if args.task:
print(f" Task: {args.task}")
if args.note:
print(f" Note: {args.note}")
# Create lightweight checkpoint delta unless disabled
if not getattr(args, 'no_checkpoint', False):
try:
create_checkpoint_delta(str(rel_path), phase.value, args.task or args.note)
except Exception as e:
# Don't fail the status update if checkpoint fails
print(f" (Checkpoint delta skipped: {e})")
return 0
def create_checkpoint_delta(dir_path: str, phase: str, action: str = None):
"""
Create a lightweight checkpoint recording the status change.
Integrates with the checkpoint system for state tracking.
"""
import subprocess
# Build notes for the checkpoint
action_str = f": {action}" if action else ""
notes = f"Status update - {dir_path} -> {phase}{action_str}"
# Call checkpoint command to create delta
result = subprocess.run(
["python3", "/opt/agent-governance/checkpoint/checkpoint.py",
"now", "--notes", notes],
capture_output=True,
text=True,
timeout=10
)
if result.returncode == 0:
# Extract checkpoint ID from output
for line in result.stdout.split('\n'):
if line.startswith("ID:"):
ckpt_id = line.split(":")[1].strip()
print(f" Checkpoint: {ckpt_id}")
break
def cmd_init(args):
"""Initialize README/STATUS in a directory."""
dir_path = Path(args.directory).resolve()
if not dir_path.exists():
print(f"Error: Directory not found: {dir_path}")
return 1
readme_path = dir_path / "README.md"
status_path = dir_path / "STATUS.md"
rel_path = dir_path.relative_to(PROJECT_ROOT) if str(dir_path).startswith(str(PROJECT_ROOT)) else dir_path
created = []
if not readme_path.exists() or args.force:
readme_path.write_text(generate_readme(dir_path))
created.append("README.md")
if not status_path.exists() or args.force:
status_path.write_text(generate_status(dir_path))
created.append("STATUS.md")
if created:
print(f"Initialized {rel_path}:")
for f in created:
print(f" Created: {f}")
else:
print(f"Files already exist in {rel_path}. Use --force to overwrite.")
return 0
def cmd_dashboard(args):
"""Show status overview of all directories."""
dirs = get_all_directories()
by_phase = {phase: [] for phase in StatusPhase}
for dir_path in dirs:
status = analyze_directory(dir_path)
by_phase[status.phase].append(status)
print("=" * 70)
print("PROJECT STATUS DASHBOARD")
print("=" * 70)
print()
total = len(dirs)
complete = len(by_phase[StatusPhase.COMPLETE])
in_progress = len(by_phase[StatusPhase.IN_PROGRESS])
blocked = len(by_phase[StatusPhase.BLOCKED])
needs_review = len(by_phase[StatusPhase.NEEDS_REVIEW])
not_started = len(by_phase[StatusPhase.NOT_STARTED])
# Progress bar
pct_complete = (complete / total * 100) if total > 0 else 0
bar_width = 40
filled = int(bar_width * complete / total) if total > 0 else 0
bar = "" * filled + "" * (bar_width - filled)
print(f"Progress: [{bar}] {pct_complete:.1f}%")
print()
# Summary counts
print(f" ✅ Complete: {complete:3d}")
print(f" 🚧 In Progress: {in_progress:3d}")
print(f" ❗ Blocked: {blocked:3d}")
print(f" ⚠️ Needs Review: {needs_review:3d}")
print(f" ⬜ Not Started: {not_started:3d}")
print(f" ─────────────────────")
print(f" Total: {total:3d}")
print()
# Show non-complete directories
if in_progress or blocked or needs_review:
print("-" * 70)
print("ACTIVE DIRECTORIES:")
print("-" * 70)
for phase in [StatusPhase.BLOCKED, StatusPhase.IN_PROGRESS, StatusPhase.NEEDS_REVIEW]:
if by_phase[phase]:
icon = STATUS_ICONS[phase]
print(f"\n{icon} {phase.value.replace('_', ' ').upper()}:")
for status in by_phase[phase]:
rel_path = status.path.relative_to(PROJECT_ROOT)
age_str = ""
if status.last_updated:
age = datetime.now(timezone.utc) - status.last_updated.replace(tzinfo=timezone.utc)
age_str = f" (updated {age.days}d ago)" if age.days > 0 else " (updated today)"
print(f" {rel_path}{age_str}")
print()
return 0
def cmd_template(args):
"""Show file templates."""
if args.type == "readme":
print(README_TEMPLATE)
elif args.type == "status":
print(STATUS_TEMPLATE)
else:
print("README.md Template:")
print("-" * 40)
print(README_TEMPLATE[:500] + "...")
print()
print("STATUS.md Template:")
print("-" * 40)
print(STATUS_TEMPLATE[:500] + "...")
return 0
# =============================================================================
# Main
# =============================================================================
def main():
parser = argparse.ArgumentParser(description="Directory Status Management")
subparsers = parser.add_subparsers(dest="command", help="Commands")
# sweep
sweep_parser = subparsers.add_parser("sweep", help="Check all directories")
sweep_parser.add_argument("--fix", action="store_true", help="Create missing files")
# update
update_parser = subparsers.add_parser("update", help="Update directory status")
update_parser.add_argument("directory", help="Directory to update")
update_parser.add_argument("--phase", help="Set phase")
update_parser.add_argument("--task", help="Add task description")
update_parser.add_argument("--note", help="Add note")
update_parser.add_argument("--deps", help="Set dependencies (comma-separated)")
update_parser.add_argument("--no-checkpoint", action="store_true",
help="Skip creating checkpoint delta")
# init
init_parser = subparsers.add_parser("init", help="Initialize directory")
init_parser.add_argument("directory", help="Directory to initialize")
init_parser.add_argument("--force", action="store_true", help="Overwrite existing")
# dashboard
subparsers.add_parser("dashboard", help="Show status overview")
# template
template_parser = subparsers.add_parser("template", help="Show templates")
template_parser.add_argument("type", nargs="?", choices=["readme", "status"],
help="Template type")
args = parser.parse_args()
if not args.command:
parser.print_help()
return 1
commands = {
"sweep": cmd_sweep,
"update": cmd_update,
"init": cmd_init,
"dashboard": cmd_dashboard,
"template": cmd_template,
}
return commands[args.command](args)
if __name__ == "__main__":
sys.exit(main())

100
checkpoint/CLAUDE.md Normal file
View File

@ -0,0 +1,100 @@
# Context Checkpoint Skill
## Overview
The checkpoint skill preserves session context across token resets and orchestrates sub-agent calls with minimal token usage.
## Commands
### Create Checkpoint
```bash
/opt/agent-governance/bin/checkpoint now [--notes "description"]
```
Captures current state: phase, tasks, dependencies, variables, recent outputs.
### Load Checkpoint
```bash
/opt/agent-governance/bin/checkpoint load [checkpoint_id]
```
Loads latest or specific checkpoint. Use `--json` for machine-readable output.
### Compare Checkpoints
```bash
/opt/agent-governance/bin/checkpoint diff [--from ID] [--to ID]
```
Shows changes between checkpoints (phase, tasks, variables, dependencies).
### List Checkpoints
```bash
/opt/agent-governance/bin/checkpoint list [--limit N]
```
Lists available checkpoints with timestamps and token counts.
### Context Summary
```bash
/opt/agent-governance/bin/checkpoint summary --level minimal|compact|standard|full
```
Generates token-aware summaries:
- `minimal` (~500 tokens): Phase + active task + agent
- `compact` (~1000 tokens): + pending tasks + key variables
- `standard` (~2000 tokens): + all tasks + dependencies
- `full` (~4000 tokens): Complete context for complex operations
### Auto-Orchestrate Mode
```bash
/opt/agent-governance/bin/checkpoint auto-orchestrate --model minimax|gemini|gemini-pro \
--instruction "command1" --instruction "command2" [--dry-run] [--confirm]
```
Delegates commands to OpenRouter models with automatic checkpointing.
### Instruction Queue
```bash
/opt/agent-governance/bin/checkpoint queue list|add|clear|pop [--instruction "..."] [--priority N]
```
Manages pending instructions for automated execution.
### Prune Old Checkpoints
```bash
/opt/agent-governance/bin/checkpoint prune [--keep N]
```
Removes old checkpoints, keeping the most recent N (default: 50).
## When to Use
1. **Before complex operations**: Create checkpoint to preserve state
2. **After session reset**: Load checkpoint to restore context
3. **Sub-agent calls**: Use `summary --level compact` to minimize tokens
4. **Debugging**: Use `diff` to see what changed between checkpoints
5. **Automated mode**: Use `auto-orchestrate` for humanless execution
## Examples
```bash
# Save current state before risky operation
checkpoint now --notes "Before database migration"
# Restore after context reset
checkpoint load
# Get minimal context for sub-agent
checkpoint summary --level minimal
# Run automated commands with Minimax
checkpoint auto-orchestrate --model minimax \
--instruction "run tests" \
--instruction "deploy if tests pass"
# Queue instructions for later
checkpoint queue add --instruction "backup database" --priority 10
checkpoint queue list
```
## Integration
Checkpoints are stored in:
- `/opt/agent-governance/checkpoint/storage/` (JSON files)
- DragonflyDB `checkpoint:latest` key (fast access)
Audit logs go to:
- SQLite: `orchestration_log` table
- DragonflyDB: `orchestration:log` list

448
checkpoint/README.md Normal file
View File

@ -0,0 +1,448 @@
# Context Checkpoint Skill
**Phase 5: Agent Bootstrapping**
A context preservation system that helps maintain session state across token window resets, CLI restarts, and sub-agent orchestration.
## Overview
The checkpoint skill provides:
1. **Periodic State Capture** - Captures phase, tasks, dependencies, variables, and outputs
2. **Token-Aware Summarization** - Creates minimal context summaries for sub-agent calls
3. **CLI Integration** - Manual and automatic checkpoint management
4. **Extensible Storage** - JSON files with DragonflyDB caching (future: remote sync)
## Quick Start
```bash
# Add to PATH (optional)
export PATH="/opt/agent-governance/bin:$PATH"
# Create a checkpoint
checkpoint now --notes "Starting task X"
# Load latest checkpoint
checkpoint load
# Compare with previous
checkpoint diff
# Get compact context summary
checkpoint summary
# List all checkpoints
checkpoint list
```
## Commands
### `checkpoint now`
Create a new checkpoint capturing current state.
```bash
checkpoint now # Basic checkpoint
checkpoint now --notes "Phase 5 start" # With notes
checkpoint now --var key value # With variables
checkpoint now --var a 1 --var b 2 # Multiple variables
checkpoint now --json # Output JSON
```
**Captures:**
- Current phase (from implementation plan)
- Task states (from governance DB)
- Dependency status (Vault, DragonflyDB, Ledger)
- Custom variables
- Recent evidence outputs
- Agent ID and tier
### `checkpoint load`
Load a checkpoint for review or restoration.
```bash
checkpoint load # Load latest
checkpoint load ckpt-20260123-120000-abc # Load specific
checkpoint load --json # Output JSON
```
### `checkpoint diff`
Compare two checkpoints to see what changed.
```bash
checkpoint diff # Latest vs previous
checkpoint diff --from ckpt-A --to ckpt-B # Specific comparison
checkpoint diff --json # Output JSON
```
**Detects:**
- Phase changes
- Task additions/removals/status changes
- Dependency status changes
- Variable additions/changes/removals
### `checkpoint list`
List available checkpoints.
```bash
checkpoint list # Last 20
checkpoint list --limit 50 # Custom limit
checkpoint list --json # Output JSON
```
### `checkpoint summary`
Generate context summary for review or sub-agent injection.
```bash
checkpoint summary # Compact (default)
checkpoint summary --level minimal # ~500 tokens
checkpoint summary --level compact # ~1000 tokens
checkpoint summary --level standard # ~2000 tokens
checkpoint summary --level full # ~4000 tokens
checkpoint summary --for terraform # Task-specific
checkpoint summary --for governance # Governance context
```
### `checkpoint prune`
Remove old checkpoints.
```bash
checkpoint prune # Keep default (50)
checkpoint prune --keep 10 # Keep only 10
```
## Token-Aware Sub-Agent Context
When orchestrating sub-agents, use the summarizer to minimize tokens while preserving essential context:
```python
from checkpoint import CheckpointManager, ContextSummarizer
# Get latest checkpoint
manager = CheckpointManager()
checkpoint = manager.get_latest_checkpoint()
# Create task-specific summary
summarizer = ContextSummarizer(checkpoint)
# For infrastructure tasks (~1000 tokens)
context = summarizer.for_subagent("terraform", max_tokens=1000)
# For governance tasks with specific variables
context = summarizer.for_subagent(
"promotion",
relevant_keys=["agent_id", "current_tier"],
max_tokens=500
)
# Pass context to sub-agent
subagent.run(context + "\n\n" + task_prompt)
```
### Summary Levels
| Level | Tokens | Contents |
|-------|--------|----------|
| `minimal` | ~500 | Phase, active task, agent, available deps |
| `compact` | ~1000 | + In-progress tasks, pending count, key vars |
| `standard` | ~2000 | + All tasks, all deps, recent outputs |
| `full` | ~4000 | + All variables, completed phases, metadata |
### Task-Specific Contexts
| Task Type | Included Context |
|-----------|-----------------|
| `terraform`, `ansible`, `infrastructure` | Infrastructure dependencies, service status |
| `database`, `query`, `ledger` | Database connections, endpoints |
| `promotion`, `revocation`, `governance` | Agent tier, governance variables |
## Auto-Checkpoint Events
Checkpoints can be automatically created by integrating with governance hooks:
```python
# In your agent code
from checkpoint import CheckpointManager
manager = CheckpointManager()
# Auto-checkpoint on phase transitions
def on_phase_complete(phase_num):
manager.create_checkpoint(
notes=f"Phase {phase_num} complete",
variables={"completed_phase": phase_num}
)
# Auto-checkpoint on task completion
def on_task_complete(task_id, result):
manager.create_checkpoint(
variables={"last_task": task_id, "result": result}
)
# Auto-checkpoint on error
def on_error(error_type, message):
manager.create_checkpoint(
notes=f"Error: {error_type}",
variables={"error_type": error_type, "error_msg": message}
)
```
## Restoring After Restart
After a CLI restart or token window reset:
```bash
# 1. Load the latest checkpoint
checkpoint load
# 2. Review what was happening
checkpoint summary --level full
# 3. If needed, compare with earlier state
checkpoint diff
# 4. Resume work with context
checkpoint summary --level compact > /tmp/context.txt
# Use context.txt as preamble for new session
```
### Programmatic Restoration
```python
from checkpoint import CheckpointManager, ContextSummarizer
manager = CheckpointManager()
checkpoint = manager.get_latest_checkpoint()
if checkpoint:
# Restore environment variables
for key, value in checkpoint.variables.items():
os.environ[f"CKPT_{key.upper()}"] = str(value)
# Get context for continuation
summarizer = ContextSummarizer(checkpoint)
context = summarizer.standard_summary()
print(f"Restored from: {checkpoint.checkpoint_id}")
print(f"Phase: {checkpoint.phase.name if checkpoint.phase else 'Unknown'}")
print(f"Tasks: {len(checkpoint.tasks)}")
```
## Storage Format
### Checkpoint JSON Structure
```json
{
"checkpoint_id": "ckpt-20260123-120000-abc12345",
"created_at": "2026-01-23T12:00:00.000000+00:00",
"session_id": "optional-session-id",
"phase": {
"name": "Phase 5: Agent Bootstrapping",
"number": 5,
"status": "in_progress",
"started_at": "2026-01-23T10:00:00+00:00",
"notes": "Working on checkpoint skill"
},
"phases_completed": [1, 2, 3, 4],
"tasks": [
{
"id": "1",
"subject": "Create checkpoint module",
"status": "completed",
"owner": null,
"blocks": [],
"blocked_by": []
}
],
"active_task_id": "2",
"dependencies": [
{
"name": "vault",
"type": "service",
"status": "available",
"endpoint": "https://127.0.0.1:8200",
"last_checked": "2026-01-23T12:00:00+00:00"
}
],
"variables": {
"custom_key": "custom_value"
},
"recent_outputs": [
{
"type": "evidence",
"id": "evd-20260123-...",
"action": "terraform",
"success": true,
"timestamp": "2026-01-23T11:30:00+00:00"
}
],
"agent_id": "my-agent",
"agent_tier": 1,
"content_hash": "abc123...",
"parent_checkpoint_id": "ckpt-20260123-110000-...",
"estimated_tokens": 450
}
```
### File Locations
```
/opt/agent-governance/checkpoint/
├── checkpoint.py # Core module
├── README.md # This documentation
├── storage/ # Checkpoint JSON files
│ ├── ckpt-20260123-120000-abc.json
│ ├── ckpt-20260123-110000-def.json
│ └── ...
└── templates/ # Future: checkpoint templates
/opt/agent-governance/bin/
└── checkpoint # CLI wrapper
```
## Extensibility
### Adding Custom State Collectors
```python
from checkpoint import CheckpointManager
class MyCheckpointManager(CheckpointManager):
def collect_my_state(self) -> dict:
# Custom state collection
return {"my_data": "..."}
def create_checkpoint(self, **kwargs):
# Add custom state to variables
custom_vars = kwargs.get("variables", {})
custom_vars.update(self.collect_my_state())
kwargs["variables"] = custom_vars
return super().create_checkpoint(**kwargs)
```
### Remote Storage (Future)
```python
# Planned: S3/remote sync
class RemoteCheckpointManager(CheckpointManager):
def __init__(self, s3_bucket: str):
super().__init__()
self.s3_bucket = s3_bucket
def save_checkpoint(self, checkpoint):
# Save locally
local_path = super().save_checkpoint(checkpoint)
# Sync to S3
self._upload_to_s3(local_path)
return local_path
def sync_from_remote(self):
# Download checkpoints from S3
pass
```
## Integration with Agent Governance
The checkpoint skill integrates with the existing governance system:
```
┌─────────────────────────────────────────────────────────────┐
│ Agent Governance │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Preflight │ │ Wrappers │ │ Evidence │ │Checkpoint│ │
│ │ Gate │ │tf/ansible│ │ Package │ │ Skill │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │ │
│ v v v v │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ DragonflyDB │ │
│ │ agent:* states | checkpoint:* | revocations:ledger │ │
│ └──────────────────────────────────────────────────────┘ │
│ │ │ │ │ │
│ v v v v │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ SQLite Ledger │ │
│ │ agent_actions | violations | promotions | tasks │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
```
## Best Practices
1. **Checkpoint Before Risky Operations**
```bash
checkpoint now --notes "Before production deployment"
```
2. **Include Relevant Variables**
```bash
checkpoint now --var target_env production --var rollback_id v1.2.3
```
3. **Use Task-Specific Summaries for Sub-Agents**
```bash
checkpoint summary --for terraform > context.txt
```
4. **Review Diffs After Long Operations**
```bash
checkpoint diff # What changed?
```
5. **Prune Regularly in Long-Running Sessions**
```bash
checkpoint prune --keep 20
```
## Troubleshooting
### "No checkpoint found"
Create one first:
```bash
checkpoint now
```
### High token estimates
Use more aggressive summarization:
```bash
checkpoint summary --level minimal
```
### Missing dependencies
Check services:
```bash
docker exec vault vault status
redis-cli -p 6379 -a governance2026 PING
```
### Stale checkpoints
Prune and recreate:
```bash
checkpoint prune --keep 5
checkpoint now --notes "Fresh start"
```

35
checkpoint/STATUS.md Normal file
View File

@ -0,0 +1,35 @@
# Status: Checkpoint
## 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:31 UTC
- **Phase**: COMPLETE
- **Action**: Context checkpoint system operational
- **Details**: Phase updated to complete
### 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:31 UTC*

1682
checkpoint/checkpoint.py Executable file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,64 @@
{
"checkpoint_id": "ckpt-20260123-202038-73c3836b",
"created_at": "2026-01-23T20:20:38.711077+00:00",
"session_id": null,
"phase": {
"name": "Phase 5: Agent Bootstrapping",
"number": 5,
"status": "in_progress",
"started_at": "2026-01-23T20:20:38.345038+00:00",
"completed_at": null,
"notes": "Phase 5 Task 1 complete: Tier 0 agent setup and tested"
},
"phases_completed": [
1,
2,
3,
4
],
"tasks": [],
"active_task_id": null,
"dependencies": [
{
"name": "vault",
"type": "service",
"status": "available",
"endpoint": "https://127.0.0.1:8200",
"last_checked": "2026-01-23T20:20:38.710037+00:00"
},
{
"name": "dragonfly",
"type": "database",
"status": "available",
"endpoint": "redis://127.0.0.1:6379",
"last_checked": "2026-01-23T20:20:38.710566+00:00"
},
{
"name": "ledger",
"type": "database",
"status": "available",
"endpoint": "/opt/agent-governance/ledger/governance.db",
"last_checked": "2026-01-23T20:20:38.710609+00:00"
}
],
"variables": {
"tier0_agent_status": "complete",
"tier0_agent_id": "tier0-agent-001",
"compliant_runs": "1",
"next_task": "sandbox_environment"
},
"recent_outputs": [
{
"type": "evidence",
"id": "evd-20260123-171822-13ed5e2e",
"action": "terraform",
"success": true,
"timestamp": "2026-01-23T17:18:22.342850+00:00"
}
],
"agent_id": null,
"agent_tier": 0,
"content_hash": "1a6c3b4a658bf143",
"parent_checkpoint_id": "ckpt-20260123-201318-b3a126db",
"estimated_tokens": 341
}

View File

@ -0,0 +1,59 @@
{
"checkpoint_id": "ckpt-20260123-202340-ef925183",
"created_at": "2026-01-23T20:23:40.278707+00:00",
"session_id": null,
"phase": {
"name": "Phase 5: Agent Bootstrapping",
"number": 5,
"status": "in_progress",
"started_at": "2026-01-23T20:23:39.983068+00:00",
"completed_at": null,
"notes": "Phase 5.2 Tier 0 Agent Setup COMPLETE. Updated implementation plan."
},
"phases_completed": [
1,
2,
3,
4
],
"tasks": [],
"active_task_id": null,
"dependencies": [
{
"name": "vault",
"type": "service",
"status": "available",
"endpoint": "https://127.0.0.1:8200",
"last_checked": "2026-01-23T20:23:40.276959+00:00"
},
{
"name": "dragonfly",
"type": "database",
"status": "available",
"endpoint": "redis://127.0.0.1:6379",
"last_checked": "2026-01-23T20:23:40.278181+00:00"
},
{
"name": "ledger",
"type": "database",
"status": "available",
"endpoint": "/opt/agent-governance/ledger/governance.db",
"last_checked": "2026-01-23T20:23:40.278240+00:00"
}
],
"variables": {},
"recent_outputs": [
{
"type": "evidence",
"id": "evd-20260123-171822-13ed5e2e",
"action": "terraform",
"success": true,
"timestamp": "2026-01-23T17:18:22.342850+00:00"
}
],
"agent_id": null,
"agent_tier": 0,
"content_hash": "0310916c45a0e75d",
"parent_checkpoint_id": "ckpt-20260123-202038-73c3836b",
"estimated_tokens": 312
}

View File

@ -0,0 +1,62 @@
{
"checkpoint_id": "ckpt-20260123-202902-fbc6e9d3",
"created_at": "2026-01-23T20:29:02.489319+00:00",
"session_id": null,
"phase": {
"name": "Phase 5: Agent Bootstrapping",
"number": 5,
"status": "in_progress",
"started_at": "2026-01-23T20:29:01.829565+00:00",
"completed_at": null,
"notes": "Testing auto-orchestrate integration"
},
"phases_completed": [
1,
2,
3,
4
],
"tasks": [],
"active_task_id": null,
"dependencies": [
{
"name": "vault",
"type": "service",
"status": "available",
"endpoint": "https://127.0.0.1:8200",
"last_checked": "2026-01-23T20:29:02.479639+00:00"
},
{
"name": "dragonfly",
"type": "database",
"status": "available",
"endpoint": "redis://127.0.0.1:6379",
"last_checked": "2026-01-23T20:29:02.480224+00:00"
},
{
"name": "ledger",
"type": "database",
"status": "available",
"endpoint": "/opt/agent-governance/ledger/governance.db",
"last_checked": "2026-01-23T20:29:02.480263+00:00"
}
],
"variables": {},
"recent_outputs": [
{
"type": "evidence",
"id": "evd-20260123-171822-13ed5e2e",
"action": "terraform",
"success": true,
"timestamp": "2026-01-23T17:18:22.342850+00:00"
}
],
"agent_id": null,
"agent_tier": 0,
"content_hash": "cc1d75580de2208b",
"parent_checkpoint_id": "ckpt-20260123-202340-ef925183",
"estimated_tokens": 327,
"orchestration_mode": "disabled",
"pending_instructions": [],
"last_model_response": null
}

View File

@ -0,0 +1,72 @@
{
"checkpoint_id": "ckpt-20260123-203100-10a4aeec",
"created_at": "2026-01-23T20:31:00.936066+00:00",
"session_id": null,
"phase": {
"name": "Phase 5: Agent Bootstrapping",
"number": 5,
"status": "in_progress",
"started_at": "2026-01-23T20:31:00.544216+00:00",
"completed_at": null,
"notes": "Phase 5.4 Automated Orchestration COMPLETE. Model Controller, checkpoint integration, audit logging all implemented."
},
"phases_completed": [
1,
2,
3,
4
],
"tasks": [],
"active_task_id": null,
"dependencies": [
{
"name": "vault",
"type": "service",
"status": "available",
"endpoint": "https://127.0.0.1:8200",
"last_checked": "2026-01-23T20:31:00.934522+00:00"
},
{
"name": "dragonfly",
"type": "database",
"status": "available",
"endpoint": "redis://127.0.0.1:6379",
"last_checked": "2026-01-23T20:31:00.935058+00:00"
},
{
"name": "ledger",
"type": "database",
"status": "available",
"endpoint": "/opt/agent-governance/ledger/governance.db",
"last_checked": "2026-01-23T20:31:00.935104+00:00"
}
],
"variables": {},
"recent_outputs": [
{
"type": "evidence",
"id": "evd-20260123-171822-13ed5e2e",
"action": "terraform",
"success": true,
"timestamp": "2026-01-23T17:18:22.342850+00:00"
}
],
"agent_id": null,
"agent_tier": 0,
"content_hash": "6810dfc1ed05ccce",
"parent_checkpoint_id": "ckpt-20260123-202902-fbc6e9d3",
"estimated_tokens": 399,
"orchestration_mode": "disabled",
"pending_instructions": [
{
"id": "cb4407a82614",
"instruction": "echo 'Hello from queue'",
"command_type": "shell",
"priority": 5,
"requires_confirmation": false,
"created_at": "2026-01-23T20:29:15.080962+00:00",
"expires_at": null
}
],
"last_model_response": null
}

View File

@ -0,0 +1,72 @@
{
"checkpoint_id": "ckpt-20260123-203608-3fdc7358",
"created_at": "2026-01-23T20:36:08.051189+00:00",
"session_id": null,
"phase": {
"name": "Phase 5: Agent Bootstrapping",
"number": 5,
"status": "in_progress",
"started_at": "2026-01-23T20:36:07.757321+00:00",
"completed_at": null,
"notes": "pytest run"
},
"phases_completed": [
1,
2,
3,
4
],
"tasks": [],
"active_task_id": null,
"dependencies": [
{
"name": "vault",
"type": "service",
"status": "available",
"endpoint": "https://127.0.0.1:8200",
"last_checked": "2026-01-23T20:36:08.049315+00:00"
},
{
"name": "dragonfly",
"type": "database",
"status": "available",
"endpoint": "redis://127.0.0.1:6379",
"last_checked": "2026-01-23T20:36:08.049848+00:00"
},
{
"name": "ledger",
"type": "database",
"status": "available",
"endpoint": "/opt/agent-governance/ledger/governance.db",
"last_checked": "2026-01-23T20:36:08.049887+00:00"
}
],
"variables": {},
"recent_outputs": [
{
"type": "evidence",
"id": "evd-20260123-171822-13ed5e2e",
"action": "terraform",
"success": true,
"timestamp": "2026-01-23T17:18:22.342850+00:00"
}
],
"agent_id": null,
"agent_tier": 0,
"content_hash": "a93260a36efc3542",
"parent_checkpoint_id": "ckpt-20260123-203100-10a4aeec",
"estimated_tokens": 372,
"orchestration_mode": "disabled",
"pending_instructions": [
{
"id": "cb4407a82614",
"instruction": "echo 'Hello from queue'",
"command_type": "shell",
"priority": 5,
"requires_confirmation": false,
"created_at": "2026-01-23T20:29:15.080962+00:00",
"expires_at": null
}
],
"last_model_response": null
}

View File

@ -0,0 +1,72 @@
{
"checkpoint_id": "ckpt-20260123-203734-99683192",
"created_at": "2026-01-23T20:37:34.527020+00:00",
"session_id": null,
"phase": {
"name": "Phase 5: Agent Bootstrapping",
"number": 5,
"status": "in_progress",
"started_at": "2026-01-23T20:37:33.892244+00:00",
"completed_at": null,
"notes": "Comprehensive tests created and passed: 59 Python tests + 34 Bun tests = 93 total"
},
"phases_completed": [
1,
2,
3,
4
],
"tasks": [],
"active_task_id": null,
"dependencies": [
{
"name": "vault",
"type": "service",
"status": "available",
"endpoint": "https://127.0.0.1:8200",
"last_checked": "2026-01-23T20:37:34.515895+00:00"
},
{
"name": "dragonfly",
"type": "database",
"status": "available",
"endpoint": "redis://127.0.0.1:6379",
"last_checked": "2026-01-23T20:37:34.522449+00:00"
},
{
"name": "ledger",
"type": "database",
"status": "available",
"endpoint": "/opt/agent-governance/ledger/governance.db",
"last_checked": "2026-01-23T20:37:34.522982+00:00"
}
],
"variables": {},
"recent_outputs": [
{
"type": "evidence",
"id": "evd-20260123-171822-13ed5e2e",
"action": "terraform",
"success": true,
"timestamp": "2026-01-23T17:18:22.342850+00:00"
}
],
"agent_id": null,
"agent_tier": 0,
"content_hash": "0a7bc6469fcd86b6",
"parent_checkpoint_id": "ckpt-20260123-203608-3fdc7358",
"estimated_tokens": 390,
"orchestration_mode": "disabled",
"pending_instructions": [
{
"id": "cb4407a82614",
"instruction": "echo 'Hello from queue'",
"command_type": "shell",
"priority": 5,
"requires_confirmation": false,
"created_at": "2026-01-23T20:29:15.080962+00:00",
"expires_at": null
}
],
"last_model_response": null
}

View File

@ -0,0 +1,72 @@
{
"checkpoint_id": "ckpt-20260123-203818-7192511e",
"created_at": "2026-01-23T20:38:18.643767+00:00",
"session_id": null,
"phase": {
"name": "Phase 5: Agent Bootstrapping",
"number": 5,
"status": "in_progress",
"started_at": "2026-01-23T20:38:18.068470+00:00",
"completed_at": null,
"notes": "Test from Claude session"
},
"phases_completed": [
1,
2,
3,
4
],
"tasks": [],
"active_task_id": null,
"dependencies": [
{
"name": "vault",
"type": "service",
"status": "available",
"endpoint": "https://127.0.0.1:8200",
"last_checked": "2026-01-23T20:38:18.631280+00:00"
},
{
"name": "dragonfly",
"type": "database",
"status": "available",
"endpoint": "redis://127.0.0.1:6379",
"last_checked": "2026-01-23T20:38:18.637653+00:00"
},
{
"name": "ledger",
"type": "database",
"status": "available",
"endpoint": "/opt/agent-governance/ledger/governance.db",
"last_checked": "2026-01-23T20:38:18.637722+00:00"
}
],
"variables": {},
"recent_outputs": [
{
"type": "evidence",
"id": "evd-20260123-171822-13ed5e2e",
"action": "terraform",
"success": true,
"timestamp": "2026-01-23T17:18:22.342850+00:00"
}
],
"agent_id": null,
"agent_tier": 0,
"content_hash": "a3c35a46a7d36be0",
"parent_checkpoint_id": "ckpt-20260123-203734-99683192",
"estimated_tokens": 376,
"orchestration_mode": "disabled",
"pending_instructions": [
{
"id": "cb4407a82614",
"instruction": "echo 'Hello from queue'",
"command_type": "shell",
"priority": 5,
"requires_confirmation": false,
"created_at": "2026-01-23T20:29:15.080962+00:00",
"expires_at": null
}
],
"last_model_response": null
}

View File

@ -0,0 +1,72 @@
{
"checkpoint_id": "ckpt-20260123-204017-297a70fc",
"created_at": "2026-01-23T20:40:17.270333+00:00",
"session_id": null,
"phase": {
"name": "Phase 5: Agent Bootstrapping",
"number": 5,
"status": "in_progress",
"started_at": "2026-01-23T20:40:16.989822+00:00",
"completed_at": null,
"notes": "pytest run"
},
"phases_completed": [
1,
2,
3,
4
],
"tasks": [],
"active_task_id": null,
"dependencies": [
{
"name": "vault",
"type": "service",
"status": "available",
"endpoint": "https://127.0.0.1:8200",
"last_checked": "2026-01-23T20:40:17.268550+00:00"
},
{
"name": "dragonfly",
"type": "database",
"status": "available",
"endpoint": "redis://127.0.0.1:6379",
"last_checked": "2026-01-23T20:40:17.268985+00:00"
},
{
"name": "ledger",
"type": "database",
"status": "available",
"endpoint": "/opt/agent-governance/ledger/governance.db",
"last_checked": "2026-01-23T20:40:17.269026+00:00"
}
],
"variables": {},
"recent_outputs": [
{
"type": "evidence",
"id": "evd-20260123-171822-13ed5e2e",
"action": "terraform",
"success": true,
"timestamp": "2026-01-23T17:18:22.342850+00:00"
}
],
"agent_id": null,
"agent_tier": 0,
"content_hash": "e92bf05927f268ee",
"parent_checkpoint_id": "ckpt-20260123-203818-7192511e",
"estimated_tokens": 372,
"orchestration_mode": "disabled",
"pending_instructions": [
{
"id": "cb4407a82614",
"instruction": "echo 'Hello from queue'",
"command_type": "shell",
"priority": 5,
"requires_confirmation": false,
"created_at": "2026-01-23T20:29:15.080962+00:00",
"expires_at": null
}
],
"last_model_response": null
}

View File

@ -0,0 +1,72 @@
{
"checkpoint_id": "ckpt-20260123-204228-96ee3ea3",
"created_at": "2026-01-23T20:42:28.854329+00:00",
"session_id": null,
"phase": {
"name": "Phase 5: Agent Bootstrapping",
"number": 5,
"status": "in_progress",
"started_at": "2026-01-23T20:42:28.519269+00:00",
"completed_at": null,
"notes": "Checkpoint skill updated to Claude format"
},
"phases_completed": [
1,
2,
3,
4
],
"tasks": [],
"active_task_id": null,
"dependencies": [
{
"name": "vault",
"type": "service",
"status": "available",
"endpoint": "https://127.0.0.1:8200",
"last_checked": "2026-01-23T20:42:28.849122+00:00"
},
{
"name": "dragonfly",
"type": "database",
"status": "available",
"endpoint": "redis://127.0.0.1:6379",
"last_checked": "2026-01-23T20:42:28.852256+00:00"
},
{
"name": "ledger",
"type": "database",
"status": "available",
"endpoint": "/opt/agent-governance/ledger/governance.db",
"last_checked": "2026-01-23T20:42:28.852317+00:00"
}
],
"variables": {},
"recent_outputs": [
{
"type": "evidence",
"id": "evd-20260123-171822-13ed5e2e",
"action": "terraform",
"success": true,
"timestamp": "2026-01-23T17:18:22.342850+00:00"
}
],
"agent_id": null,
"agent_tier": 0,
"content_hash": "2627e06fd50b021d",
"parent_checkpoint_id": "ckpt-20260123-204017-297a70fc",
"estimated_tokens": 380,
"orchestration_mode": "disabled",
"pending_instructions": [
{
"id": "cb4407a82614",
"instruction": "echo 'Hello from queue'",
"command_type": "shell",
"priority": 5,
"requires_confirmation": false,
"created_at": "2026-01-23T20:29:15.080962+00:00",
"expires_at": null
}
],
"last_model_response": null
}

View File

@ -0,0 +1,72 @@
{
"checkpoint_id": "ckpt-20260123-204317-002c0556",
"created_at": "2026-01-23T20:43:17.577418+00:00",
"session_id": null,
"phase": {
"name": "Phase 5: Agent Bootstrapping",
"number": 5,
"status": "in_progress",
"started_at": "2026-01-23T20:43:17.326770+00:00",
"completed_at": null,
"notes": "Phase 5 complete: Checkpoint skill, Tier 0 agent, Automated orchestration, All 93 tests passing. Moving to next phase."
},
"phases_completed": [
1,
2,
3,
4
],
"tasks": [],
"active_task_id": null,
"dependencies": [
{
"name": "vault",
"type": "service",
"status": "available",
"endpoint": "https://127.0.0.1:8200",
"last_checked": "2026-01-23T20:43:17.576012+00:00"
},
{
"name": "dragonfly",
"type": "database",
"status": "available",
"endpoint": "redis://127.0.0.1:6379",
"last_checked": "2026-01-23T20:43:17.576503+00:00"
},
{
"name": "ledger",
"type": "database",
"status": "available",
"endpoint": "/opt/agent-governance/ledger/governance.db",
"last_checked": "2026-01-23T20:43:17.576557+00:00"
}
],
"variables": {},
"recent_outputs": [
{
"type": "evidence",
"id": "evd-20260123-171822-13ed5e2e",
"action": "terraform",
"success": true,
"timestamp": "2026-01-23T17:18:22.342850+00:00"
}
],
"agent_id": null,
"agent_tier": 0,
"content_hash": "b064f39f348a8671",
"parent_checkpoint_id": "ckpt-20260123-204228-96ee3ea3",
"estimated_tokens": 399,
"orchestration_mode": "disabled",
"pending_instructions": [
{
"id": "cb4407a82614",
"instruction": "echo 'Hello from queue'",
"command_type": "shell",
"priority": 5,
"requires_confirmation": false,
"created_at": "2026-01-23T20:29:15.080962+00:00",
"expires_at": null
}
],
"last_model_response": null
}

View File

@ -0,0 +1,72 @@
{
"checkpoint_id": "ckpt-20260123-205212-fe2de0ea",
"created_at": "2026-01-23T20:52:12.527011+00:00",
"session_id": null,
"phase": {
"name": "Phase 5: Agent Bootstrapping",
"number": 5,
"status": "in_progress",
"started_at": "2026-01-23T20:52:12.164101+00:00",
"completed_at": null,
"notes": "Ledger API complete: FastAPI service with endpoints for agents, actions, violations, promotions, orchestration. All tests passing."
},
"phases_completed": [
1,
2,
3,
4
],
"tasks": [],
"active_task_id": null,
"dependencies": [
{
"name": "vault",
"type": "service",
"status": "available",
"endpoint": "https://127.0.0.1:8200",
"last_checked": "2026-01-23T20:52:12.518894+00:00"
},
{
"name": "dragonfly",
"type": "database",
"status": "available",
"endpoint": "redis://127.0.0.1:6379",
"last_checked": "2026-01-23T20:52:12.520025+00:00"
},
{
"name": "ledger",
"type": "database",
"status": "available",
"endpoint": "/opt/agent-governance/ledger/governance.db",
"last_checked": "2026-01-23T20:52:12.520071+00:00"
}
],
"variables": {},
"recent_outputs": [
{
"type": "evidence",
"id": "evd-20260123-171822-13ed5e2e",
"action": "terraform",
"success": true,
"timestamp": "2026-01-23T17:18:22.342850+00:00"
}
],
"agent_id": null,
"agent_tier": 0,
"content_hash": "3334307059b66282",
"parent_checkpoint_id": "ckpt-20260123-204317-002c0556",
"estimated_tokens": 402,
"orchestration_mode": "disabled",
"pending_instructions": [
{
"id": "cb4407a82614",
"instruction": "echo 'Hello from queue'",
"command_type": "shell",
"priority": 5,
"requires_confirmation": false,
"created_at": "2026-01-23T20:29:15.080962+00:00",
"expires_at": null
}
],
"last_model_response": null
}

View File

@ -0,0 +1,72 @@
{
"checkpoint_id": "ckpt-20260123-205809-3f767f30",
"created_at": "2026-01-23T20:58:09.486758+00:00",
"session_id": null,
"phase": {
"name": "Phase 5: Agent Bootstrapping",
"number": 5,
"status": "in_progress",
"started_at": "2026-01-23T20:58:08.787328+00:00",
"completed_at": null,
"notes": "Dashboard wired to Ledger API"
},
"phases_completed": [
1,
2,
3,
4
],
"tasks": [],
"active_task_id": null,
"dependencies": [
{
"name": "vault",
"type": "service",
"status": "available",
"endpoint": "https://127.0.0.1:8200",
"last_checked": "2026-01-23T20:58:09.482419+00:00"
},
{
"name": "dragonfly",
"type": "database",
"status": "available",
"endpoint": "redis://127.0.0.1:6379",
"last_checked": "2026-01-23T20:58:09.484394+00:00"
},
{
"name": "ledger",
"type": "database",
"status": "available",
"endpoint": "/opt/agent-governance/ledger/governance.db",
"last_checked": "2026-01-23T20:58:09.484465+00:00"
}
],
"variables": {},
"recent_outputs": [
{
"type": "evidence",
"id": "evd-20260123-171822-13ed5e2e",
"action": "terraform",
"success": true,
"timestamp": "2026-01-23T17:18:22.342850+00:00"
}
],
"agent_id": null,
"agent_tier": 0,
"content_hash": "ba6083b89d87a793",
"parent_checkpoint_id": "ckpt-20260123-205212-fe2de0ea",
"estimated_tokens": 377,
"orchestration_mode": "disabled",
"pending_instructions": [
{
"id": "cb4407a82614",
"instruction": "echo 'Hello from queue'",
"command_type": "shell",
"priority": 5,
"requires_confirmation": false,
"created_at": "2026-01-23T20:29:15.080962+00:00",
"expires_at": null
}
],
"last_model_response": null
}

View File

@ -0,0 +1,72 @@
{
"checkpoint_id": "ckpt-20260123-210116-eacedbb8",
"created_at": "2026-01-23T21:01:16.613016+00:00",
"session_id": null,
"phase": {
"name": "Phase 5: Agent Bootstrapping",
"number": 5,
"status": "in_progress",
"started_at": "2026-01-23T21:01:15.962087+00:00",
"completed_at": null,
"notes": "Dashboard wired to Ledger API - orchestration panel complete"
},
"phases_completed": [
1,
2,
3,
4
],
"tasks": [],
"active_task_id": null,
"dependencies": [
{
"name": "vault",
"type": "service",
"status": "available",
"endpoint": "https://127.0.0.1:8200",
"last_checked": "2026-01-23T21:01:16.610972+00:00"
},
{
"name": "dragonfly",
"type": "database",
"status": "available",
"endpoint": "redis://127.0.0.1:6379",
"last_checked": "2026-01-23T21:01:16.611527+00:00"
},
{
"name": "ledger",
"type": "database",
"status": "available",
"endpoint": "/opt/agent-governance/ledger/governance.db",
"last_checked": "2026-01-23T21:01:16.611585+00:00"
}
],
"variables": {},
"recent_outputs": [
{
"type": "evidence",
"id": "evd-20260123-171822-13ed5e2e",
"action": "terraform",
"success": true,
"timestamp": "2026-01-23T17:18:22.342850+00:00"
}
],
"agent_id": null,
"agent_tier": 0,
"content_hash": "ab892c373fed9038",
"parent_checkpoint_id": "ckpt-20260123-205809-3f767f30",
"estimated_tokens": 385,
"orchestration_mode": "disabled",
"pending_instructions": [
{
"id": "cb4407a82614",
"instruction": "echo 'Hello from queue'",
"command_type": "shell",
"priority": 5,
"requires_confirmation": false,
"created_at": "2026-01-23T20:29:15.080962+00:00",
"expires_at": null
}
],
"last_model_response": null
}

View File

@ -0,0 +1,62 @@
{
"checkpoint_id": "ckpt-20260123-210655-6dc8a4d2",
"created_at": "2026-01-23T21:06:55.826327+00:00",
"session_id": null,
"phase": {
"name": "Phase 5: Agent Bootstrapping",
"number": 5,
"status": "in_progress",
"started_at": "2026-01-23T21:06:55.141732+00:00",
"completed_at": null,
"notes": "Queue cleared"
},
"phases_completed": [
1,
2,
3,
4
],
"tasks": [],
"active_task_id": null,
"dependencies": [
{
"name": "vault",
"type": "service",
"status": "available",
"endpoint": "https://127.0.0.1:8200",
"last_checked": "2026-01-23T21:06:55.822357+00:00"
},
{
"name": "dragonfly",
"type": "database",
"status": "available",
"endpoint": "redis://127.0.0.1:6379",
"last_checked": "2026-01-23T21:06:55.823252+00:00"
},
{
"name": "ledger",
"type": "database",
"status": "available",
"endpoint": "/opt/agent-governance/ledger/governance.db",
"last_checked": "2026-01-23T21:06:55.823306+00:00"
}
],
"variables": {},
"recent_outputs": [
{
"type": "evidence",
"id": "evd-20260123-171822-13ed5e2e",
"action": "terraform",
"success": true,
"timestamp": "2026-01-23T17:18:22.342850+00:00"
}
],
"agent_id": null,
"agent_tier": 0,
"content_hash": "7c9b7ad46b7395be",
"parent_checkpoint_id": "ckpt-20260123-210116-eacedbb8",
"estimated_tokens": 322,
"orchestration_mode": "disabled",
"pending_instructions": [],
"last_model_response": null
}

View File

@ -0,0 +1,62 @@
{
"checkpoint_id": "ckpt-20260123-211157-fc6c392b",
"created_at": "2026-01-23T21:11:57.475724+00:00",
"session_id": null,
"phase": {
"name": "Phase 5: Agent Bootstrapping",
"number": 5,
"status": "in_progress",
"started_at": "2026-01-23T21:11:56.581669+00:00",
"completed_at": null,
"notes": "Apache Spark deployed - master and worker running on port 9944"
},
"phases_completed": [
1,
2,
3,
4
],
"tasks": [],
"active_task_id": null,
"dependencies": [
{
"name": "vault",
"type": "service",
"status": "available",
"endpoint": "https://127.0.0.1:8200",
"last_checked": "2026-01-23T21:11:57.467264+00:00"
},
{
"name": "dragonfly",
"type": "database",
"status": "available",
"endpoint": "redis://127.0.0.1:6379",
"last_checked": "2026-01-23T21:11:57.470895+00:00"
},
{
"name": "ledger",
"type": "database",
"status": "available",
"endpoint": "/opt/agent-governance/ledger/governance.db",
"last_checked": "2026-01-23T21:11:57.470956+00:00"
}
],
"variables": {},
"recent_outputs": [
{
"type": "evidence",
"id": "evd-20260123-171822-13ed5e2e",
"action": "terraform",
"success": true,
"timestamp": "2026-01-23T17:18:22.342850+00:00"
}
],
"agent_id": null,
"agent_tier": 0,
"content_hash": "f927a8659cb4090b",
"parent_checkpoint_id": "ckpt-20260123-210655-6dc8a4d2",
"estimated_tokens": 334,
"orchestration_mode": "disabled",
"pending_instructions": [],
"last_model_response": null
}

View File

@ -0,0 +1,62 @@
{
"checkpoint_id": "ckpt-20260123-211825-1e246710",
"created_at": "2026-01-23T21:18:25.457265+00:00",
"session_id": null,
"phase": {
"name": "Phase 5: Agent Bootstrapping",
"number": 5,
"status": "in_progress",
"started_at": "2026-01-23T21:18:24.603424+00:00",
"completed_at": null,
"notes": "Multi-agent orchestrator tested - 3 proposals generated for Spark monitoring"
},
"phases_completed": [
1,
2,
3,
4
],
"tasks": [],
"active_task_id": null,
"dependencies": [
{
"name": "vault",
"type": "service",
"status": "available",
"endpoint": "https://127.0.0.1:8200",
"last_checked": "2026-01-23T21:18:25.454113+00:00"
},
{
"name": "dragonfly",
"type": "database",
"status": "available",
"endpoint": "redis://127.0.0.1:6379",
"last_checked": "2026-01-23T21:18:25.454674+00:00"
},
{
"name": "ledger",
"type": "database",
"status": "available",
"endpoint": "/opt/agent-governance/ledger/governance.db",
"last_checked": "2026-01-23T21:18:25.454718+00:00"
}
],
"variables": {},
"recent_outputs": [
{
"type": "evidence",
"id": "evd-20260123-171822-13ed5e2e",
"action": "terraform",
"success": true,
"timestamp": "2026-01-23T17:18:22.342850+00:00"
}
],
"agent_id": null,
"agent_tier": 0,
"content_hash": "cc21525fd285b6f2",
"parent_checkpoint_id": "ckpt-20260123-211157-fc6c392b",
"estimated_tokens": 337,
"orchestration_mode": "disabled",
"pending_instructions": [],
"last_model_response": null
}

View File

@ -0,0 +1,62 @@
{
"checkpoint_id": "ckpt-20260123-213633-bfa34c01",
"created_at": "2026-01-23T21:36:33.639782+00:00",
"session_id": null,
"phase": {
"name": "Phase 5: Agent Bootstrapping",
"number": 5,
"status": "in_progress",
"started_at": "2026-01-23T21:36:33.369563+00:00",
"completed_at": null,
"notes": "pytest run"
},
"phases_completed": [
1,
2,
3,
4
],
"tasks": [],
"active_task_id": null,
"dependencies": [
{
"name": "vault",
"type": "service",
"status": "available",
"endpoint": "https://127.0.0.1:8200",
"last_checked": "2026-01-23T21:36:33.637912+00:00"
},
{
"name": "dragonfly",
"type": "database",
"status": "available",
"endpoint": "redis://127.0.0.1:6379",
"last_checked": "2026-01-23T21:36:33.638392+00:00"
},
{
"name": "ledger",
"type": "database",
"status": "available",
"endpoint": "/opt/agent-governance/ledger/governance.db",
"last_checked": "2026-01-23T21:36:33.638433+00:00"
}
],
"variables": {},
"recent_outputs": [
{
"type": "evidence",
"id": "evd-20260123-171822-13ed5e2e",
"action": "terraform",
"success": true,
"timestamp": "2026-01-23T17:18:22.342850+00:00"
}
],
"agent_id": null,
"agent_tier": 0,
"content_hash": "e1c9d5658fefbf29",
"parent_checkpoint_id": "ckpt-20260123-211825-1e246710",
"estimated_tokens": 321,
"orchestration_mode": "disabled",
"pending_instructions": [],
"last_model_response": null
}

View File

@ -0,0 +1,62 @@
{
"checkpoint_id": "ckpt-20260123-213647-f6bba4a7",
"created_at": "2026-01-23T21:36:47.850468+00:00",
"session_id": null,
"phase": {
"name": "Phase 5: Agent Bootstrapping",
"number": 5,
"status": "in_progress",
"started_at": "2026-01-23T21:36:47.550206+00:00",
"completed_at": null,
"notes": "Full test suite passed: 93/93 tests (59 Python, 34 Bun/TS)"
},
"phases_completed": [
1,
2,
3,
4
],
"tasks": [],
"active_task_id": null,
"dependencies": [
{
"name": "vault",
"type": "service",
"status": "available",
"endpoint": "https://127.0.0.1:8200",
"last_checked": "2026-01-23T21:36:47.843205+00:00"
},
{
"name": "dragonfly",
"type": "database",
"status": "available",
"endpoint": "redis://127.0.0.1:6379",
"last_checked": "2026-01-23T21:36:47.844685+00:00"
},
{
"name": "ledger",
"type": "database",
"status": "available",
"endpoint": "/opt/agent-governance/ledger/governance.db",
"last_checked": "2026-01-23T21:36:47.844739+00:00"
}
],
"variables": {},
"recent_outputs": [
{
"type": "evidence",
"id": "evd-20260123-171822-13ed5e2e",
"action": "terraform",
"success": true,
"timestamp": "2026-01-23T17:18:22.342850+00:00"
}
],
"agent_id": null,
"agent_tier": 0,
"content_hash": "ac3e8a49fc74ddc0",
"parent_checkpoint_id": "ckpt-20260123-213633-bfa34c01",
"estimated_tokens": 333,
"orchestration_mode": "disabled",
"pending_instructions": [],
"last_model_response": null
}

View File

@ -0,0 +1,62 @@
{
"checkpoint_id": "ckpt-20260123-213850-de7c07dd",
"created_at": "2026-01-23T21:38:50.577370+00:00",
"session_id": null,
"phase": {
"name": "Phase 5: Agent Bootstrapping",
"number": 5,
"status": "in_progress",
"started_at": "2026-01-23T21:38:49.757777+00:00",
"completed_at": null,
"notes": "Plan approved and executed: Spark deployment on localhost:9944. Promotion: 2/5 plans, 2/3 consecutive."
},
"phases_completed": [
1,
2,
3,
4
],
"tasks": [],
"active_task_id": null,
"dependencies": [
{
"name": "vault",
"type": "service",
"status": "available",
"endpoint": "https://127.0.0.1:8200",
"last_checked": "2026-01-23T21:38:50.573392+00:00"
},
{
"name": "dragonfly",
"type": "database",
"status": "available",
"endpoint": "redis://127.0.0.1:6379",
"last_checked": "2026-01-23T21:38:50.574184+00:00"
},
{
"name": "ledger",
"type": "database",
"status": "available",
"endpoint": "/opt/agent-governance/ledger/governance.db",
"last_checked": "2026-01-23T21:38:50.574278+00:00"
}
],
"variables": {},
"recent_outputs": [
{
"type": "evidence",
"id": "evd-20260123-171822-13ed5e2e",
"action": "terraform",
"success": true,
"timestamp": "2026-01-23T17:18:22.342850+00:00"
}
],
"agent_id": null,
"agent_tier": 0,
"content_hash": "d83142fbb874b6bc",
"parent_checkpoint_id": "ckpt-20260123-213647-f6bba4a7",
"estimated_tokens": 344,
"orchestration_mode": "disabled",
"pending_instructions": [],
"last_model_response": null
}

View File

@ -0,0 +1,62 @@
{
"checkpoint_id": "ckpt-20260123-214125-56507255",
"created_at": "2026-01-23T21:41:25.626196+00:00",
"session_id": null,
"phase": {
"name": "Phase 5: Agent Bootstrapping",
"number": 5,
"status": "in_progress",
"started_at": "2026-01-23T21:41:24.821684+00:00",
"completed_at": null,
"notes": "All plans executed. Tier 0 agent promotion eligible: 8/5 compliant runs, 8/3 consecutive. Services: Spark, Redis, Nginx, Prometheus."
},
"phases_completed": [
1,
2,
3,
4
],
"tasks": [],
"active_task_id": null,
"dependencies": [
{
"name": "vault",
"type": "service",
"status": "available",
"endpoint": "https://127.0.0.1:8200",
"last_checked": "2026-01-23T21:41:25.623230+00:00"
},
{
"name": "dragonfly",
"type": "database",
"status": "available",
"endpoint": "redis://127.0.0.1:6379",
"last_checked": "2026-01-23T21:41:25.623746+00:00"
},
{
"name": "ledger",
"type": "database",
"status": "available",
"endpoint": "/opt/agent-governance/ledger/governance.db",
"last_checked": "2026-01-23T21:41:25.623786+00:00"
}
],
"variables": {},
"recent_outputs": [
{
"type": "evidence",
"id": "evd-20260123-171822-13ed5e2e",
"action": "terraform",
"success": true,
"timestamp": "2026-01-23T17:18:22.342850+00:00"
}
],
"agent_id": null,
"agent_tier": 0,
"content_hash": "bfce2f494ef95c58",
"parent_checkpoint_id": "ckpt-20260123-213850-de7c07dd",
"estimated_tokens": 351,
"orchestration_mode": "disabled",
"pending_instructions": [],
"last_model_response": null
}

View File

@ -0,0 +1,62 @@
{
"checkpoint_id": "ckpt-20260123-215213-be4db8e4",
"created_at": "2026-01-23T21:52:13.490710+00:00",
"session_id": null,
"phase": {
"name": "Phase 5: Agent Bootstrapping",
"number": 5,
"status": "in_progress",
"started_at": "2026-01-23T21:52:12.561194+00:00",
"completed_at": null,
"notes": "Ground truth plan created: Spark Logging Pipeline (Lambda arch with Kafka/Flink/ClickHouse). LLM-generated via OpenRouter/claude-sonnet-4. Score: 0.85"
},
"phases_completed": [
1,
2,
3,
4
],
"tasks": [],
"active_task_id": null,
"dependencies": [
{
"name": "vault",
"type": "service",
"status": "available",
"endpoint": "https://127.0.0.1:8200",
"last_checked": "2026-01-23T21:52:13.485629+00:00"
},
{
"name": "dragonfly",
"type": "database",
"status": "available",
"endpoint": "redis://127.0.0.1:6379",
"last_checked": "2026-01-23T21:52:13.486928+00:00"
},
{
"name": "ledger",
"type": "database",
"status": "available",
"endpoint": "/opt/agent-governance/ledger/governance.db",
"last_checked": "2026-01-23T21:52:13.487002+00:00"
}
],
"variables": {},
"recent_outputs": [
{
"type": "evidence",
"id": "evd-20260123-171822-13ed5e2e",
"action": "terraform",
"success": true,
"timestamp": "2026-01-23T17:18:22.342850+00:00"
}
],
"agent_id": null,
"agent_tier": 0,
"content_hash": "cfe07e5b0f048b35",
"parent_checkpoint_id": "ckpt-20260123-214125-56507255",
"estimated_tokens": 356,
"orchestration_mode": "disabled",
"pending_instructions": [],
"last_model_response": null
}

Some files were not shown because too many files have changed in this diff Show More