/** * Project-level agent identity, override and learning layer. * * Every built-in roster agent has a base definition (role + prompt + tools + * skills) in the catalog. On top of that, **each project** can: * * 1. Override prompt, tools, skills, budget per role * 2. Accumulate learned wisdom specific to this codebase * 3. Attach a project-custom identity (name, avatar, tone) * * Resolution cascade (most → least specific): * activeLearning.json → project-identity.md → catalog base * * Files live under `.wrongstack/agents//`: * config.json — static overrides (tools, budget, skillNames) * identity.md — custom prompt appendix (tone, project-specific rules) * learned.md — auto-generated wisdom from past sessions * knowledge.md — current-needs checklist (what versions to verify today) */ import { existsSync } from 'node:fs'; import type { SubagentConfig } from '../../types/multi-agent.js'; export interface ProjectAgentConfig { /** * Static tool allowlist override. When set, replaces the catalog tools * entirely (not merged). Omit to keep catalog defaults. */ tools?: string[] | undefined; /** Static skill name override. Replaces catalog skillNames entirely. */ skillNames?: string[] | undefined; /** * Budget overrides. Each field individually overrides the catalog budget. */ budget?: { timeoutMs?: number | undefined; idleTimeoutMs?: number | undefined; maxIterations?: number | undefined; maxToolCalls?: number | undefined; maxTokens?: number | undefined; maxCostUsd?: number | undefined; } | undefined; /** Provider/model override for this role within the project. */ provider?: string | undefined; model?: string | undefined; modelPolicy?: { allowed: Array<{ provider: string; model: string; }>; fallbacks?: Array<{ provider: string; model: string; }> | undefined; strict?: boolean | undefined; } | undefined; fallbackProfile?: string | undefined; /** Project-relative directory, resolved inside the assigned checkout/worktree. */ cwd?: string | undefined; worktree?: boolean | 'auto' | 'required' | 'off' | undefined; availability?: { timezone: string; days: number[]; start: string; end: string; mode?: 'advisory' | 'enforce' | undefined; } | undefined; /** * Allowed capability overrides. Replaces catalog capabilities entirely. */ allowedCapabilities?: readonly string[] | undefined; } /** Durable definition for a project-created roster role. */ export interface ProjectAgentProfile { role: string; name: string; baseRole: string; purpose: string; taskTypes: string[]; createdAt: string; updatedAt: string; } export interface CreateProjectAgentInput { role?: string | undefined; name: string; purpose: string; taskTypes: string[]; baseRole?: string | undefined; } /** Validate untrusted JSON before it can alter a roster agent runtime. */ export declare function validateProjectAgentConfig(value: unknown): ProjectAgentConfig; /** Persistent controls and counters for one roster role's learning loop. */ export interface ProjectAgentLearningPolicy { /** Retain knowledge but stop prompt injection and automatic capture when false. */ enabled: boolean; lifetimeCaptureCount: number; lastCaptureAt?: string | undefined; lastCaptureSource?: 'automatic' | 'manual' | 'taught' | undefined; } export interface LearnedCaptureResult { role: string; captured: number; skipped: number; status: 'captured' | 'disabled' | 'empty_output' | 'no_blocks' | 'guarded' | 'quality_rejected'; reason?: string | undefined; } export declare function assertProjectAgentRole(role: string): string; /** * Current-knowledge manifest for a role: what live facts the agent should * fetch on every spawn to avoid hallucinating stale versions. */ export interface RoleKnowledgeManifest { role: string; /** Registry endpoints to query at spawn time, keyed by topic. */ liveQueries: Record; /** * Human-readable checklist: "before answering questions about X, verify Y * from the live registry". Injected verbatim into the subagent prompt. */ checklist: string[]; /** * Minimum confidence threshold before the role should re-verify a live * source rather than relying on its own training data (0.0–1.0). Default 0.5. */ verifyThreshold: number; } export declare function slugifyProjectAgentRole(name: string): string; export declare function loadProjectAgentProfile(role: string, projectRoot?: string): ProjectAgentProfile | undefined; /** Create a new, independently-learning project role from a roster template. */ export declare function createProjectAgent(input: CreateProjectAgentInput, projectRoot?: string): ProjectAgentProfile; /** * Live roster overlay. Project policy is resolved lazily for both built-in and * project-created roles, so WebUI changes take effect on the next spawn without * rebuilding the Director. Built-in roles keep their safety floors; custom * roles may opt into a deliberately narrow runtime. */ export declare function createProjectAgentRoster(baseRoster: Record, projectRoot?: string): Record; export declare function loadProjectAgentLearningPolicy(role: string, projectRoot?: string): ProjectAgentLearningPolicy; export declare function updateProjectAgentLearningPolicy(role: string, patch: Partial>, projectRoot?: string): ProjectAgentLearningPolicy; /** * Load the project-level agent config for a given role. * Returns `undefined` when no project override exists. */ export declare function loadProjectAgentConfig(role: string, projectRoot?: string): ProjectAgentConfig | undefined; /** * Load the project-level identity appendix for a given role. * Appended to the subagent prompt after the base role prompt and policy. * Returns the empty string when no identity file exists. */ export declare function loadProjectAgentIdentity(role: string, projectRoot?: string): string; /** * Load the learning-derived wisdom for a given role. * Appended to the subagent prompt as a "knowledge from past sessions" block. * The learning file is auto-generated by the `memory-curator` or * `self-improving` agent roles and contains de-duplicated, curated findings. */ export declare function loadProjectAgentLearned(role: string, projectRoot?: string): string; /** * Load the current-knowledge checklist for a given role. * Returns the built-in directive if no project override exists. * The checklist guides the agent on what to verify from live registries * before answering questions in its domain. */ export declare function loadRoleKnowledgeManifest(role: string, projectRoot?: string): RoleKnowledgeManifest | undefined; /** * Merge a project agent config onto a base `SubagentConfig`. * Returns a new config; does not mutate either input. */ export declare function applyProjectAgentConfig(base: SubagentConfig, projectConfig: ProjectAgentConfig | undefined, options?: { protectSystemRole?: boolean | undefined; }): SubagentConfig; /** * Build the full project-contextualized prompt for a given role. * * Cascade, from start to end: * 1. Base role prompt from instruction files (agentPrompt(id)) * 2. Technology policy (appended by agentPrompt) * 3. Project identity appendix (identity.md) * 4. Learned wisdom (learned.md) * 5. Live-knowledge checklist */ /** * Write or update the learned wisdom file for a given role. * Appends to existing content when `mode` is 'append'; replaces when * it is 'replace'. Returns the full path written so callers can log it. */ export declare function updateProjectAgentLearned(role: string, content: string, projectRoot?: string, mode?: 'append' | 'replace'): string; /** * Write or update the project identity file for a given role. * Replaces any existing identity.md with the new content. */ export declare function updateProjectAgentIdentity(role: string, content: string, projectRoot?: string): string; /** * Write or update the config.json override for a given role. */ export declare function updateProjectAgentConfig(role: string, config: ProjectAgentConfig, projectRoot?: string): string; /** * Write or update the knowledge manifest for a given role. */ export declare function updateProjectAgentKnowledge(role: string, manifest: RoleKnowledgeManifest, projectRoot?: string): string; /** * Reset all project-level customizations for a given role back to factory * defaults by removing its `.wrongstack/agents//` directory. * When `role` is omitted or `'*'`, resets all roles. * Returns a list of paths that were removed. */ export declare function resetProjectAgentIdentity(role?: string, projectRoot?: string): string[]; /** * Improve (or refresh) the project-custom identity for a given role * by clearing the learned.md and identity.md and re-scaffolding empty * templates. This is the explicit "refresh" trigger a user can call * when they want the project agent to re-learn from scratch. * * Returns a status report string. */ export declare function refreshProjectAgentIdentity(role: string, projectRoot?: string): string; export declare function buildProjectContextualizedPrompt(basePrompt: string, role: string, projectRoot?: string, options?: { identityOverride?: string | undefined; }): string; export declare const LEARNED_SOFT_LIMIT = 8192; export declare const LEARNED_HARD_LIMIT = 16384; /** * Maximum length (in characters) of a single captured learning entry after * normalization. Entries longer than this are truncated to the first few * instructive sentences. Keeps `learned.md` scannable and prevents agents * from dumping journal-style narratives into the buffer. */ export declare const LEARNED_ENTRY_MAX_CHARS = 600; /** * Category tag for a captured learning entry. Derived deterministically from * the entry's content so the file is self-describing and scannable. * * - `convention` — durable rule / standard ("Always run X", "Never assume Y") * - `pattern` — reusable approach ("Use X for Y", "Prefer A over B") * - `warning` — pitfall / anti-pattern ("Avoid X", "Beware Y") * - `fact` — stable project fact (default when no directive verb matches) */ export type LearnedEntryCategory = 'convention' | 'pattern' | 'warning' | 'fact'; /** * Normalize a captured LEARNED block into an instructive entry. * * Removes ephemeral artifacts (commit SHAs, timestamps, line numbers, PR refs), * strips narrative framing sentences ("When I did X...", "Today I found..."), * and enforces a maximum length. Returns `null` when the block is too thin, * too narrative, or otherwise cannot be salvaged into actionable guidance — * in which case the capture pipeline should reject it rather than persist a * session-log entry that masquerades as learned data. * * This function is deliberately conservative: when unsure, it preserves the * content. The goal is to remove clearly-noisy artifacts, not to rewrite the * agent's lesson. */ export declare function normalizeLearnedEntry(raw: string): { text: string; category: LearnedEntryCategory; } | null; /** * Classify an instructive entry into one of four category buckets so the * `learned.md` file is self-describing and scannable. Order of checks is * intentional: warnings are the highest-signal category and win ties. */ export declare function classifyLearnedEntry(text: string): LearnedEntryCategory; export declare const CAPTURE_COOLDOWN_MS = 120000; export declare const CAPTURE_MAX_PER_SESSION = 3; /** * Check whether a new capture is allowed for this role. Returns a rejection * reason string when blocked, or undefined when capture may proceed. */ export declare function canCaptureNewLearned(role: string, existingSize: number, isManual: boolean, projectRoot?: string): string | undefined; /** * Per-role learning stats for monitoring UIs. */ export interface ProjectAgentLearnStats { role: string; exists: boolean; entryCount: number; totalBytes: number; lastCapture: string | null; lastCaptureTimestamp: number | null; cooldownRemainingMs: number; sessionCaptureCount: number; needsSummarization: boolean; learnedPath: string | null; identityPath: string | null; hasIdentity: boolean; hasConfig: boolean; hasKnowledge: boolean; learningEnabled: boolean; lifetimeCaptureCount: number; lastCaptureSource: ProjectAgentLearningPolicy['lastCaptureSource'] | null; /** Whether a consolidated document exists for this role. */ isConsolidated: boolean; /** Consolidation metadata, if a consolidation has been performed. */ consolidation?: ConsolidationMetadata | undefined; } export declare function getProjectAgentLearnStats(role: string, projectRoot?: string): ProjectAgentLearnStats; /** * List every role that has any project-level agent customization or policy. */ export declare function listProjectAgentRoles(projectRoot?: string): string[]; /** * Re-export existsSync so callers can check file existence without importing fs. */ export { existsSync }; /** * Detect semantic conflicts between different roles' learned wisdom. * Returns entries where token overlap ≥ 0.60, which suggests the two * roles have learned about the same topic — potentially contradicting. */ export declare function detectLearnedConflicts(projectRoot?: string): Array<{ roleA: string; roleB: string; snippetA: string; snippetB: string; similarity: number; detectedAt: string; }>; export declare function listProjectAgentLearnedEntries(role: string, projectRoot?: string): string[]; /** * Parsed representation of a single buffered entry — the structured shape * that every capture merges into. */ export interface StructuredLearnedEntry { /** Injective content identity: normalized token signature for dedup. */ key: string; /** Category bucket (convention / pattern / warning / fact). */ category: LearnedEntryCategory; /** The directive itself — what the agent should do. */ what: string; /** Why this directive exists — derived from category and directive signals. */ why: string; /** Concrete, runnable anchor — commands, file paths, package names. */ how: string; /** ISO timestamp of when this entry was originally captured. */ capturedAt: string; } /** * Parse the stamp line at the top of a buffered entry. Handles three formats: * * 1. New structured-document format (current): * `` * 2. Legacy stamped format: `> [category] Captured ` / `> Captured ` * 3. Legacy taught format: `> [category] Taught ` / `> Taught ` * * Buffers persisted before the structured-document migration still load * cleanly through this parser. */ export declare function parseLearnedEntryStamp(entry: string): { capturedAt: string; category: LearnedEntryCategory | null; }; /** * Decompose a directive into its "what / why / how" components. * * Most captured LEARNED blocks are a single sentence — "Always run pnpm * typecheck before declaring work complete." This function heuristically * pulls out the action (what), the implicit reason (why, derived from * the entry's category and directive verbs), and any concrete anchors * (how, extracted as commands, file paths, and package names). * * The decomposition is deliberately conservative: when an entry already * reads cleanly as a single imperative, the "what" carries the full text * and "why"/"how" are short complementary notes. The agent reading the * buffer can scan the "what" lines for relevance and dive into "why" / * "how" only when applying the rule. */ export declare function decomposeLearnedEntry(text: string, category: LearnedEntryCategory): { what: string; why: string; how: string; }; /** * Parse every buffered entry into a structured representation, skipping * malformed lines and structural-document chunks (the header, section * headings, footer) that don't carry a per-entry stamp. Entries with no * parseable directive body are dropped — the merge pipeline re-derives * category from the directive text so old entries that lost their stamp * still load. */ export declare function parseStructuredLearnedEntries(role: string, projectRoot?: string): StructuredLearnedEntry[]; /** * Merge a new directive into an existing structured-entry list, deduplicating * by content similarity. Returns the merged list with the new entry replacing * its near-duplicate when one is found (newest write wins). */ export declare function mergeStructuredEntries(existing: StructuredLearnedEntry[], fresh: { text: string; category: LearnedEntryCategory; capturedAt: string; }): StructuredLearnedEntry[]; /** * Render a structured-entry list as the markdown instruction document that * gets written to `learned.md`. The output is intentionally a single * structured reference — not a journal — so a future agent reading it can * scan section headings and pull the rules it needs. * * Each entry embeds its category and capture timestamp as a hidden HTML * comment (``) so the parser can recover that * metadata when re-loading the buffer. The comment is invisible when the * file is rendered as markdown — agents reading the document see only the * structured "what / why / how" content. */ export declare function renderLearnedInstructions(role: string, entries: StructuredLearnedEntry[], capturedAt: string): string; /** * Learned-wisdom capture from agent output. * * Scans `output` for `## LEARNED` blocks and persists each unique, * quality-passing block to the role's `learned.md`. Each block is run * through `normalizeLearnedEntry` so the buffer only accumulates instructive * directives — narrative session logs are rejected at capture time. Near- * duplicate entries (token overlap ≥ 0.55) are silently skipped. * * When the resulting file would exceed `LEARNED_HARD_LIMIT` (16 KB) * the oldest entries are rotated out before writing. * * @returns the number of **new** items actually persisted (0 if none). */ export declare function captureLearnedFromAgentOutput(output: string, role: string, projectRoot?: string, isManual?: boolean): number; export declare function captureLearnedFromAgentOutputDetailed(output: string, role: string, projectRoot?: string, isManual?: boolean): LearnedCaptureResult; /** * Signal the host that a background summarisation pass is warranted. * Called by `captureLearnedFromAgentOutput` when the **new** learned size * crosses `LEARNED_SOFT_LIMIT`. The host is responsible for scheduling a * low-priority consolidation task (typically via the Brain or a shadow agent). * * The summary _mechanism_ is intentionally shallow here — the real * summarisation is an LLM call that should run as a deferred, uncritical * fleet task so it never blocks a user-facing operation. * * @returns a summary brief for the host, or '' when no action is needed. */ export declare function hintLearnedNeedsSummarization(role: string, projectRoot?: string): string; export interface ConsolidationMetadata { /** ISO timestamp of the last consolidation. */ consolidatedAt: string; /** Number of raw learned.md entries that were synthesized. */ sourceEntryCount: number; /** Byte size of the raw learned.md at consolidation time. */ sourceBytes: number; /** Byte size of the resulting consolidated.md. */ consolidatedBytes: number; /** Whether the consolidation was user-triggered or automatic. */ trigger: 'manual' | 'automatic'; /** Optional model that produced the consolidation. */ model?: string | undefined; } /** * Load the consolidated learning document for a role. * Returns '' when no consolidation has been performed. */ export declare function loadProjectAgentConsolidated(role: string, projectRoot?: string): string; /** * Load consolidation metadata for a role. * Returns undefined when no consolidation has been performed. */ export declare function loadConsolidationMetadata(role: string, projectRoot?: string): ConsolidationMetadata | undefined; /** * Whether a role has a valid consolidated document. */ export declare function isConsolidated(role: string, projectRoot?: string): boolean; /** * Save the consolidated document and its metadata atomically. * Returns the path to consolidated.md. */ export declare function saveProjectAgentConsolidated(role: string, content: string, projectRoot?: string, metadata?: Partial): string; /** * Clear the consolidated document and its metadata (e.g. when raw entries * are deleted or reset). Idempotent — safe to call when no consolidation * exists. */ export declare function clearProjectAgentConsolidated(role: string, projectRoot?: string): void; /** * Build the LLM instruction for consolidating a role's learned entries. * * The instruction tells the reviewing LLM to: * 1. Read every raw learned.md entry * 2. Synthesize them into a single narrowly-scoped document * 3. Preserve every fact, convention, and decision — no omissions * 4. Rewrite verbose entries into concise, directive statements * * The caller (WS handler / CLI / WebUI) is responsible for executing the * LLM call and passing the result to saveProjectAgentConsolidated(). */ export declare function buildConsolidationInstruction(role: string, projectRoot?: string): { instruction: string; rawEntries: string[]; hasExistingConsolidation: boolean; }; //# sourceMappingURL=project-agent-identity.d.ts.map