Direction shift 2026-04-22: docs/CONTROL_PLANE_PRD.md becomes the long-horizon architecture target. Existing Lakehouse (docs/PRD.md, Phases 0-37) is preserved as the reference implementation and first consumer. New 6-layer architecture: L1 Universal API /v1/chat /v1/usage /v1/sessions /v1/tools /v1/context L2 Routing & Policy Engine (rules, fallback chains, cost gating) L3 Provider Adapter Layer (Ollama + OpenRouter + Gemini + Claude) L4 Knowledge + Memory + Playbooks (already built) L5 Execution Loop (scenarios + bot/cycle.ts instances) L6 Observability + token accounting Phases 38-44 sequenced with detailed per-phase specs in the PRD. Current scope: staffing domain (synthetic workers_500k, contracts, emails, SMS, playbooks). DevOps (Terraform/Ansible) is long-horizon target — architecture-compatible but not current. Files added: - docs/CONTROL_PLANE_PRD.md — 6-layer architecture, Phase 38-44 sequencing with staffing-first Truth Layer + Validation pipeline - bot/ — manual-only PR bot scaffold. First consumer test-bed for /v1/chat (Phase 38). Mem0-aligned ADD/UPDATE/NOOP apply semantics; KB feedback loop reads prior cycles on same gap and injects into cloud prompt so bot cycles compound like scenario.ts runs do. - tests/multi-agent/run_stress.ts — the 6-task diverse stress test referenced in the previous commit but missing from its staging Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
71 lines
3.8 KiB
TypeScript
71 lines
3.8 KiB
TypeScript
// ═══════════════════════════════════════════════════════════════════
|
|
// YOU WRITE THIS FILE. These four functions shape the bot's entire
|
|
// behavior. Defaults below are safe and intentionally conservative —
|
|
// redline to taste. Each function is ~5-10 lines.
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
import type { CycleContext, CycleResult, Gap, Proposal } from "./types.ts";
|
|
|
|
// Should this cycle even fire right now?
|
|
// Called AFTER the pause-file check + cost-cap check have already passed.
|
|
// Use this for judgment calls (cooldown, shared-resource conflicts).
|
|
export function shouldRunCycle(ctx: CycleContext): { run: boolean; reason: string } {
|
|
if (ctx.workingTreeDirty) {
|
|
return { run: false, reason: "dirty working tree — commit or stash your work first" };
|
|
}
|
|
if (ctx.lastCycleAt) {
|
|
const minsAgo = (Date.now() - new Date(ctx.lastCycleAt).getTime()) / 60000;
|
|
if (minsAgo < 10) return { run: false, reason: `last cycle ${minsAgo.toFixed(1)}min ago (<10min cooldown)` };
|
|
}
|
|
if (ctx.autotuneBusy) {
|
|
return { run: false, reason: "autotune agent is running — avoid Ollama contention" };
|
|
}
|
|
return { run: true, reason: "ready" };
|
|
}
|
|
|
|
// Which [bot-eligible] gap from the PRD does this cycle attack?
|
|
// Default: first in document order. Alternatives: random for exploration,
|
|
// smallest-context, tag-priority (e.g. [bot-eligible:high] wins).
|
|
export function pickGap(gaps: Gap[]): { gap: Gap | null; reason: string } {
|
|
if (gaps.length === 0) return { gap: null, reason: "no [bot-eligible] gaps in PRD" };
|
|
return { gap: gaps[0], reason: "first eligible gap in document order" };
|
|
}
|
|
|
|
// Cheap gate BEFORE burning test cycles. Reject if proposal smells wrong.
|
|
// This catches runaway cloud proposals before we apply+test them.
|
|
export function scoreProposal(p: Proposal): { accept: boolean; reason: string } {
|
|
if (p.files.length === 0) return { accept: false, reason: "empty proposal" };
|
|
if (p.files.length > 5) return { accept: false, reason: `>5 files (${p.files.length})` };
|
|
if (p.estimated_loc > 200) return { accept: false, reason: `>200 LOC (${p.estimated_loc})` };
|
|
|
|
// Paths the bot is NEVER allowed to touch. Extend cautiously.
|
|
const forbidden = [
|
|
".git/", "secrets", "lakehouse.toml",
|
|
"docs/ADR-", "docs/DECISIONS.md", "docs/PRD.md",
|
|
"/etc/", "/root/", "Cargo.lock",
|
|
];
|
|
for (const f of p.files) {
|
|
const hit = forbidden.find(prefix => f.path.includes(prefix));
|
|
if (hit) return { accept: false, reason: `touches forbidden path (${hit}): ${f.path}` };
|
|
}
|
|
return { accept: true, reason: "passes size + path filters" };
|
|
}
|
|
|
|
// Tests are green and changes applied. Do we actually open a PR?
|
|
// Mem0-aligned: the cycle already short-circuited with `cycle_noop` if
|
|
// every file was identical to current state, so by the time we're here
|
|
// at least one file was actually added or updated.
|
|
//
|
|
// Stricter options below — uncomment to require a test file touched, etc.
|
|
export function shouldOpenPR(c: CycleResult): { open: boolean; reason: string } {
|
|
if (!c.testsGreen) return { open: false, reason: "tests red" };
|
|
const realChanges = c.filesAdded.length + c.filesUpdated.length;
|
|
if (realChanges === 0) return { open: false, reason: "nothing actually changed on disk" };
|
|
|
|
// Stricter option — require at least one test file in the diff:
|
|
// const hasTest = [...c.filesAdded, ...c.filesUpdated].some(f => /test|_tests/i.test(f));
|
|
// if (!hasTest) return { open: false, reason: "no test file touched" };
|
|
|
|
return { open: true, reason: `green + ${c.filesAdded.length} add / ${c.filesUpdated.length} update` };
|
|
}
|