titan-agent 5.6.6 → 5.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -0
- package/dist/agent/agentLoop.js +29 -13
- package/dist/agent/agentLoop.js.map +1 -1
- package/dist/agent/promptBudget.js +33 -3
- package/dist/agent/promptBudget.js.map +1 -1
- package/dist/utils/constants.js +1 -1
- package/dist/utils/constants.js.map +1 -1
- package/docs/HARNESS-PATTERNS.md +92 -0
- package/package.json +1 -1
- package/ui/dist/sw.js +1 -1
|
@@ -48,8 +48,30 @@ function checkBudget(sessionId, config) {
|
|
|
48
48
|
const status = getBudgetStatus(sessionId, config);
|
|
49
49
|
if (status.max <= 0) return null;
|
|
50
50
|
if (status.exceeded) {
|
|
51
|
-
logger.warn(COMPONENT, `Budget EXCEEDED for ${sessionId}: ${status.used}/${status.max} tokens`);
|
|
52
|
-
|
|
51
|
+
logger.warn(COMPONENT, `Budget EXCEEDED for ${sessionId}: ${status.used}/${status.max} tokens (action=${config.action})`);
|
|
52
|
+
if (config.action === "compress") {
|
|
53
|
+
return {
|
|
54
|
+
action: "compress",
|
|
55
|
+
message: `Context budget hit (${status.used.toLocaleString()}/${status.max.toLocaleString()} tokens). Trimming older turns and continuing.`,
|
|
56
|
+
used: status.used,
|
|
57
|
+
max: status.max
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
if (config.action === "downgrade") {
|
|
61
|
+
return {
|
|
62
|
+
action: "downgrade",
|
|
63
|
+
message: `Context budget hit (${status.used.toLocaleString()}/${status.max.toLocaleString()} tokens). Downgrading model to ${config.downgradeModel ?? "cheaper alternative"}.`,
|
|
64
|
+
used: status.used,
|
|
65
|
+
max: status.max,
|
|
66
|
+
downgradeModel: config.downgradeModel
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
action: "stop",
|
|
71
|
+
message: `Token budget exceeded (${status.used.toLocaleString()}/${status.max.toLocaleString()}). Session paused to control costs.`,
|
|
72
|
+
used: status.used,
|
|
73
|
+
max: status.max
|
|
74
|
+
};
|
|
53
75
|
}
|
|
54
76
|
if (!status.warned && status.pct >= config.warningThreshold) {
|
|
55
77
|
const state = budgets.get(sessionId);
|
|
@@ -58,6 +80,13 @@ function checkBudget(sessionId, config) {
|
|
|
58
80
|
}
|
|
59
81
|
return null;
|
|
60
82
|
}
|
|
83
|
+
function resetBudgetUsage(sessionId) {
|
|
84
|
+
const state = budgets.get(sessionId);
|
|
85
|
+
if (!state) return;
|
|
86
|
+
state.used = 0;
|
|
87
|
+
state.warned = false;
|
|
88
|
+
state.exceeded = false;
|
|
89
|
+
}
|
|
61
90
|
function markExceeded(sessionId) {
|
|
62
91
|
const state = budgets.get(sessionId);
|
|
63
92
|
if (state) state.exceeded = true;
|
|
@@ -80,6 +109,7 @@ export {
|
|
|
80
109
|
getDefaultBudget,
|
|
81
110
|
initBudget,
|
|
82
111
|
markExceeded,
|
|
83
|
-
recordUsage
|
|
112
|
+
recordUsage,
|
|
113
|
+
resetBudgetUsage
|
|
84
114
|
};
|
|
85
115
|
//# sourceMappingURL=promptBudget.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/agent/promptBudget.ts"],"sourcesContent":["/**\n * TITAN — Prompt Budget System (Space Agent parity)\n *\n * Enforces per-session token budgets to prevent runaway costs.\n * Tracks prompt + completion tokens across rounds and applies\n * graceful degradation when budgets are exceeded.\n */\n\nimport logger from '../utils/logger.js';\n\nconst COMPONENT = 'PromptBudget';\n\nexport interface BudgetConfig {\n /** Maximum tokens allowed for this session (0 = unlimited) */\n maxTokens: number;\n /** Soft warning threshold (0.8 = warn at 80%) */\n warningThreshold: number;\n /** Action when budget exceeded: 'stop' | 'downgrade' | 'compress' */\n action: 'stop' | 'downgrade' | 'compress';\n /** Downgrade target model when action='downgrade' */\n downgradeModel?: string;\n}\n\ninterface BudgetState {\n used: number;\n warned: boolean;\n exceeded: boolean;\n createdAt: number;\n}\n\nconst budgets = new Map<string, BudgetState>();\n\n/** Default budget from config (can be overridden per session) */\nexport function getDefaultBudget(config: { agent?: { maxTokens?: number } }): BudgetConfig {\n return {\n maxTokens: config.agent?.maxTokens || 0,\n warningThreshold: 0.8,\n action: 'compress',\n };\n}\n\n/** Initialize a budget for a session */\nexport function initBudget(sessionId: string, config?: Partial<BudgetConfig>): BudgetConfig {\n const budget: BudgetConfig = {\n maxTokens: config?.maxTokens ?? 0,\n warningThreshold: config?.warningThreshold ?? 0.8,\n action: config?.action ?? 'compress',\n downgradeModel: config?.downgradeModel,\n };\n budgets.set(sessionId, {\n used: 0,\n warned: false,\n exceeded: false,\n createdAt: Date.now(),\n });\n logger.info(COMPONENT, `Budget initialized for ${sessionId}: max=${budget.maxTokens}, action=${budget.action}`);\n return budget;\n}\n\n/** Record token usage for a session */\nexport function recordUsage(sessionId: string, promptTokens: number, completionTokens: number): void {\n const state = budgets.get(sessionId);\n if (!state) return;\n state.used += promptTokens + completionTokens;\n}\n\n/** Get current budget status */\nexport function getBudgetStatus(sessionId: string, config: BudgetConfig): {\n used: number;\n max: number;\n pct: number;\n warned: boolean;\n exceeded: boolean;\n} {\n const state = budgets.get(sessionId);\n if (!state || config.maxTokens <= 0) {\n return { used: state?.used || 0, max: 0, pct: 0, warned: false, exceeded: false };\n }\n const pct = state.used / config.maxTokens;\n return {\n used: state.used,\n max: config.maxTokens,\n pct,\n warned: state.warned,\n exceeded: state.exceeded || pct >= 1.0,\n };\n}\n\n
|
|
1
|
+
{"version":3,"sources":["../../src/agent/promptBudget.ts"],"sourcesContent":["/**\n * TITAN — Prompt Budget System (Space Agent parity)\n *\n * Enforces per-session token budgets to prevent runaway costs.\n * Tracks prompt + completion tokens across rounds and applies\n * graceful degradation when budgets are exceeded.\n */\n\nimport logger from '../utils/logger.js';\n\nconst COMPONENT = 'PromptBudget';\n\nexport interface BudgetConfig {\n /** Maximum tokens allowed for this session (0 = unlimited) */\n maxTokens: number;\n /** Soft warning threshold (0.8 = warn at 80%) */\n warningThreshold: number;\n /** Action when budget exceeded: 'stop' | 'downgrade' | 'compress' */\n action: 'stop' | 'downgrade' | 'compress';\n /** Downgrade target model when action='downgrade' */\n downgradeModel?: string;\n}\n\ninterface BudgetState {\n used: number;\n warned: boolean;\n exceeded: boolean;\n createdAt: number;\n}\n\nconst budgets = new Map<string, BudgetState>();\n\n/** Default budget from config (can be overridden per session) */\nexport function getDefaultBudget(config: { agent?: { maxTokens?: number } }): BudgetConfig {\n return {\n maxTokens: config.agent?.maxTokens || 0,\n warningThreshold: 0.8,\n action: 'compress',\n };\n}\n\n/** Initialize a budget for a session */\nexport function initBudget(sessionId: string, config?: Partial<BudgetConfig>): BudgetConfig {\n const budget: BudgetConfig = {\n maxTokens: config?.maxTokens ?? 0,\n warningThreshold: config?.warningThreshold ?? 0.8,\n action: config?.action ?? 'compress',\n downgradeModel: config?.downgradeModel,\n };\n budgets.set(sessionId, {\n used: 0,\n warned: false,\n exceeded: false,\n createdAt: Date.now(),\n });\n logger.info(COMPONENT, `Budget initialized for ${sessionId}: max=${budget.maxTokens}, action=${budget.action}`);\n return budget;\n}\n\n/** Record token usage for a session */\nexport function recordUsage(sessionId: string, promptTokens: number, completionTokens: number): void {\n const state = budgets.get(sessionId);\n if (!state) return;\n state.used += promptTokens + completionTokens;\n}\n\n/** Get current budget status */\nexport function getBudgetStatus(sessionId: string, config: BudgetConfig): {\n used: number;\n max: number;\n pct: number;\n warned: boolean;\n exceeded: boolean;\n} {\n const state = budgets.get(sessionId);\n if (!state || config.maxTokens <= 0) {\n return { used: state?.used || 0, max: 0, pct: 0, warned: false, exceeded: false };\n }\n const pct = state.used / config.maxTokens;\n return {\n used: state.used,\n max: config.maxTokens,\n pct,\n warned: state.warned,\n exceeded: state.exceeded || pct >= 1.0,\n };\n}\n\n/**\n * Result of a budget check. `null` means the request is within budget and\n * should proceed unchanged. A non-null result tells the caller (the agent\n * loop) how to honor the configured action — compress, downgrade, or stop.\n *\n * Pre-v5.7.0 this function returned a single message string for ALL three\n * actions, and the loop hard-stopped no matter what. So `action: 'compress'`\n * (the default) silently behaved exactly like `action: 'stop'`. The user-\n * facing symptom was a 5-turn conversation hitting \"Session paused to\n * control costs.\" when in fact the user had asked for graceful compression.\n * Reference: Anthropic \"Effective context engineering for AI agents\"\n * (context-as-bottleneck), 12 Factor Agents §10 \"small, focused agents\".\n */\nexport type BudgetCheckResult =\n | null\n | {\n /** Which action to take next. */\n action: 'compress' | 'downgrade' | 'stop';\n /** Human-readable explanation (logged + may surface in UI). */\n message: string;\n /** Snapshot of where the budget stood when the action fired. */\n used: number;\n max: number;\n /** Target model for `downgrade`. */\n downgradeModel?: string;\n };\n\n/** Check budget before an LLM call. */\nexport function checkBudget(sessionId: string, config: BudgetConfig): BudgetCheckResult {\n const status = getBudgetStatus(sessionId, config);\n if (status.max <= 0) return null;\n\n if (status.exceeded) {\n logger.warn(COMPONENT, `Budget EXCEEDED for ${sessionId}: ${status.used}/${status.max} tokens (action=${config.action})`);\n if (config.action === 'compress') {\n return {\n action: 'compress',\n message: `Context budget hit (${status.used.toLocaleString()}/${status.max.toLocaleString()} tokens). Trimming older turns and continuing.`,\n used: status.used,\n max: status.max,\n };\n }\n if (config.action === 'downgrade') {\n return {\n action: 'downgrade',\n message: `Context budget hit (${status.used.toLocaleString()}/${status.max.toLocaleString()} tokens). Downgrading model to ${config.downgradeModel ?? 'cheaper alternative'}.`,\n used: status.used,\n max: status.max,\n downgradeModel: config.downgradeModel,\n };\n }\n // 'stop' — old hard-pause behavior, only used when the caller\n // explicitly opts in. Default remains 'compress'.\n return {\n action: 'stop',\n message: `Token budget exceeded (${status.used.toLocaleString()}/${status.max.toLocaleString()}). Session paused to control costs.`,\n used: status.used,\n max: status.max,\n };\n }\n\n if (!status.warned && status.pct >= config.warningThreshold) {\n const state = budgets.get(sessionId);\n if (state) state.warned = true;\n logger.warn(COMPONENT, `Budget warning for ${sessionId}: ${(status.pct * 100).toFixed(0)}% used`);\n }\n\n return null;\n}\n\n/**\n * Reset the used-token counter for a session. Called after a successful\n * compression so the next round starts with a smaller footprint and\n * doesn't immediately re-trigger the budget gate.\n */\nexport function resetBudgetUsage(sessionId: string): void {\n const state = budgets.get(sessionId);\n if (!state) return;\n state.used = 0;\n state.warned = false;\n state.exceeded = false;\n}\n\n/** Mark budget as exceeded (called when action is taken) */\nexport function markExceeded(sessionId: string): void {\n const state = budgets.get(sessionId);\n if (state) state.exceeded = true;\n}\n\n/** Clean up budget state for a session */\nexport function cleanupBudget(sessionId: string): void {\n budgets.delete(sessionId);\n}\n\n/** Get a budget summary for all active sessions */\nexport function getActiveBudgets(): Array<{ sessionId: string; used: number; createdAt: number }> {\n return Array.from(budgets.entries()).map(([sessionId, state]) => ({\n sessionId,\n used: state.used,\n createdAt: state.createdAt,\n }));\n}\n"],"mappings":";AAQA,OAAO,YAAY;AAEnB,MAAM,YAAY;AAoBlB,MAAM,UAAU,oBAAI,IAAyB;AAGtC,SAAS,iBAAiB,QAA0D;AACvF,SAAO;AAAA,IACH,WAAW,OAAO,OAAO,aAAa;AAAA,IACtC,kBAAkB;AAAA,IAClB,QAAQ;AAAA,EACZ;AACJ;AAGO,SAAS,WAAW,WAAmB,QAA8C;AACxF,QAAM,SAAuB;AAAA,IACzB,WAAW,QAAQ,aAAa;AAAA,IAChC,kBAAkB,QAAQ,oBAAoB;AAAA,IAC9C,QAAQ,QAAQ,UAAU;AAAA,IAC1B,gBAAgB,QAAQ;AAAA,EAC5B;AACA,UAAQ,IAAI,WAAW;AAAA,IACnB,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,WAAW,KAAK,IAAI;AAAA,EACxB,CAAC;AACD,SAAO,KAAK,WAAW,0BAA0B,SAAS,SAAS,OAAO,SAAS,YAAY,OAAO,MAAM,EAAE;AAC9G,SAAO;AACX;AAGO,SAAS,YAAY,WAAmB,cAAsB,kBAAgC;AACjG,QAAM,QAAQ,QAAQ,IAAI,SAAS;AACnC,MAAI,CAAC,MAAO;AACZ,QAAM,QAAQ,eAAe;AACjC;AAGO,SAAS,gBAAgB,WAAmB,QAMjD;AACE,QAAM,QAAQ,QAAQ,IAAI,SAAS;AACnC,MAAI,CAAC,SAAS,OAAO,aAAa,GAAG;AACjC,WAAO,EAAE,MAAM,OAAO,QAAQ,GAAG,KAAK,GAAG,KAAK,GAAG,QAAQ,OAAO,UAAU,MAAM;AAAA,EACpF;AACA,QAAM,MAAM,MAAM,OAAO,OAAO;AAChC,SAAO;AAAA,IACH,MAAM,MAAM;AAAA,IACZ,KAAK,OAAO;AAAA,IACZ;AAAA,IACA,QAAQ,MAAM;AAAA,IACd,UAAU,MAAM,YAAY,OAAO;AAAA,EACvC;AACJ;AA8BO,SAAS,YAAY,WAAmB,QAAyC;AACpF,QAAM,SAAS,gBAAgB,WAAW,MAAM;AAChD,MAAI,OAAO,OAAO,EAAG,QAAO;AAE5B,MAAI,OAAO,UAAU;AACjB,WAAO,KAAK,WAAW,uBAAuB,SAAS,KAAK,OAAO,IAAI,IAAI,OAAO,GAAG,mBAAmB,OAAO,MAAM,GAAG;AACxH,QAAI,OAAO,WAAW,YAAY;AAC9B,aAAO;AAAA,QACH,QAAQ;AAAA,QACR,SAAS,uBAAuB,OAAO,KAAK,eAAe,CAAC,IAAI,OAAO,IAAI,eAAe,CAAC;AAAA,QAC3F,MAAM,OAAO;AAAA,QACb,KAAK,OAAO;AAAA,MAChB;AAAA,IACJ;AACA,QAAI,OAAO,WAAW,aAAa;AAC/B,aAAO;AAAA,QACH,QAAQ;AAAA,QACR,SAAS,uBAAuB,OAAO,KAAK,eAAe,CAAC,IAAI,OAAO,IAAI,eAAe,CAAC,kCAAkC,OAAO,kBAAkB,qBAAqB;AAAA,QAC3K,MAAM,OAAO;AAAA,QACb,KAAK,OAAO;AAAA,QACZ,gBAAgB,OAAO;AAAA,MAC3B;AAAA,IACJ;AAGA,WAAO;AAAA,MACH,QAAQ;AAAA,MACR,SAAS,0BAA0B,OAAO,KAAK,eAAe,CAAC,IAAI,OAAO,IAAI,eAAe,CAAC;AAAA,MAC9F,MAAM,OAAO;AAAA,MACb,KAAK,OAAO;AAAA,IAChB;AAAA,EACJ;AAEA,MAAI,CAAC,OAAO,UAAU,OAAO,OAAO,OAAO,kBAAkB;AACzD,UAAM,QAAQ,QAAQ,IAAI,SAAS;AACnC,QAAI,MAAO,OAAM,SAAS;AAC1B,WAAO,KAAK,WAAW,sBAAsB,SAAS,MAAM,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,QAAQ;AAAA,EACpG;AAEA,SAAO;AACX;AAOO,SAAS,iBAAiB,WAAyB;AACtD,QAAM,QAAQ,QAAQ,IAAI,SAAS;AACnC,MAAI,CAAC,MAAO;AACZ,QAAM,OAAO;AACb,QAAM,SAAS;AACf,QAAM,WAAW;AACrB;AAGO,SAAS,aAAa,WAAyB;AAClD,QAAM,QAAQ,QAAQ,IAAI,SAAS;AACnC,MAAI,MAAO,OAAM,WAAW;AAChC;AAGO,SAAS,cAAc,WAAyB;AACnD,UAAQ,OAAO,SAAS;AAC5B;AAGO,SAAS,mBAAkF;AAC9F,SAAO,MAAM,KAAK,QAAQ,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC,WAAW,KAAK,OAAO;AAAA,IAC9D;AAAA,IACA,MAAM,MAAM;AAAA,IACZ,WAAW,MAAM;AAAA,EACrB,EAAE;AACN;","names":[]}
|
package/dist/utils/constants.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { homedir } from "os";
|
|
3
3
|
import { join } from "path";
|
|
4
|
-
const TITAN_VERSION = "5.
|
|
4
|
+
const TITAN_VERSION = "5.7.1";
|
|
5
5
|
const TITAN_CODENAME = "Spacewalk";
|
|
6
6
|
const TITAN_NAME = "TITAN";
|
|
7
7
|
const TITAN_FULL_NAME = "The Intelligent Task Automation Network";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils/constants.ts"],"sourcesContent":["/**\n * TITAN Constants\n */\nimport { homedir } from 'os';\nimport { join } from 'path';\n\nexport const TITAN_VERSION = '5.
|
|
1
|
+
{"version":3,"sources":["../../src/utils/constants.ts"],"sourcesContent":["/**\n * TITAN Constants\n */\nimport { homedir } from 'os';\nimport { join } from 'path';\n\nexport const TITAN_VERSION = '5.7.1';\nexport const TITAN_CODENAME = 'Spacewalk';\nexport const TITAN_NAME = 'TITAN';\nexport const TITAN_FULL_NAME = 'The Intelligent Task Automation Network';\nexport const TITAN_ASCII_LOGO = `\n╔══════════════════════════════════════════════════════╗\n║ ║\n║ ████████╗██╗████████╗ █████╗ ███╗ ██╗ ║\n║ ██║ ██║ ██║ ██╔══██╗████╗ ██║ ║\n║ ██║ ██║ ██║ ███████║██╔██╗ ██║ ║\n║ ██║ ██║ ██║ ██╔══██║██║╚██╗██║ ║\n║ ██║ ██║ ██║ ██║ ██║██║ ╚████║ ║\n║ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═══╝ ║\n║ ║\n║ The Intelligent Task Automation Network ║\n║ v${TITAN_VERSION} • by Tony Elliott ║\n╚══════════════════════════════════════════════════════╝`;\n\n// Paths\n// Hunt Finding #03 (2026-04-14): honor TITAN_HOME env var if set.\n// Previously this was hardcoded to `~/.titan`, which meant:\n// - Docker containers couldn't override the config path\n// - Shared machines couldn't isolate per-user state\n// - Test fixtures couldn't run against an isolated home\n// - The systemd unit's `Environment=TITAN_HOME=...` was silently ignored\n// The env var is read once at module load (constants are resolved at import time).\n// If TITAN_HOME starts with `~/`, expand it to the user's home dir.\nfunction resolveTitanHome(): string {\n const envHome = process.env.TITAN_HOME;\n if (envHome && envHome.trim().length > 0) {\n const trimmed = envHome.trim();\n if (trimmed.startsWith('~/')) {\n return join(homedir(), trimmed.slice(2));\n }\n if (trimmed === '~') {\n return homedir();\n }\n return trimmed;\n }\n return join(homedir(), '.titan');\n}\nexport const TITAN_HOME = resolveTitanHome();\nexport const TITAN_CONFIG_PATH = join(TITAN_HOME, 'titan.json');\nexport const TITAN_DB_PATH = join(TITAN_HOME, 'titan.db');\nexport const TITAN_WORKSPACE = join(TITAN_HOME, 'workspace');\nexport const TITAN_SKILLS_DIR = join(TITAN_WORKSPACE, 'skills');\nexport const TITAN_LOGS_DIR = join(TITAN_HOME, 'logs');\nexport const TITAN_MEMORY_DIR = join(TITAN_HOME, 'memory');\n\n// Workspace prompt files (injected into agent context)\nexport const AGENTS_MD = join(TITAN_WORKSPACE, 'AGENTS.md');\nexport const SOUL_MD = join(TITAN_WORKSPACE, 'SOUL.md');\nexport const TOOLS_MD = join(TITAN_WORKSPACE, 'TOOLS.md');\nexport const TITAN_MD_FILENAME = 'TITAN.md';\nexport const AUTOPILOT_MD = join(TITAN_HOME, 'AUTOPILOT.md');\nexport const AUTOPILOT_RUNS_PATH = join(TITAN_HOME, 'autopilot-runs.jsonl');\nexport const TITAN_CREDENTIALS_DIR = join(TITAN_HOME, 'credentials');\n\n// Income & lead tracking\nexport const INCOME_LEDGER_PATH = join(TITAN_HOME, 'income-ledger.jsonl');\nexport const FREELANCE_LEADS_PATH = join(TITAN_HOME, 'freelance-leads.jsonl');\nexport const FREELANCE_PROFILE_PATH = join(TITAN_HOME, 'freelance-profile.json');\nexport const LEADS_PATH = join(TITAN_HOME, 'leads.jsonl');\nexport const TELEMETRY_EVENTS_PATH = join(TITAN_HOME, 'telemetry-events.jsonl');\nexport const SOMADRIVE_STATE_PATH = join(TITAN_HOME, 'soma-drive-state.json');\nexport const ACTIVITY_LOG_PATH = join(TITAN_HOME, 'activity-log.jsonl');\n\n// Gateway defaults\nexport const DEFAULT_GATEWAY_HOST = '0.0.0.0';\nexport const DEFAULT_GATEWAY_PORT = 48420;\nexport const DEFAULT_WEB_PORT = 48421;\n\n// Agent defaults\nexport const DEFAULT_MODEL = 'anthropic/claude-sonnet-4-20250514';\n/** v5.4.1: User-preference ceiling. Providers clamp per-model via\n * clampMaxTokens() so this can be high without causing 400s on\n * capped endpoints (e.g. Claude Sonnet 4 8K, Cohere 4K). */\nexport const DEFAULT_MAX_TOKENS = 200000;\nexport const DEFAULT_TEMPERATURE = 0.7;\nexport const MAX_CONTEXT_MESSAGES = 50;\nexport const SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes\n\n// Security\nexport const DEFAULT_SANDBOX_MODE = 'host';\n/** Default allowed tools. Empty = allow ALL registered tools.\n * Use security.deniedTools to block specific tools instead. */\nexport const ALLOWED_TOOLS_DEFAULT: string[] = [];\nexport const DENIED_TOOLS_DEFAULT: string[] = [];\n"],"mappings":";AAGA,SAAS,eAAe;AACxB,SAAS,YAAY;AAEd,MAAM,gBAAgB;AACtB,MAAM,iBAAiB;AACvB,MAAM,aAAa;AACnB,MAAM,kBAAkB;AACxB,MAAM,mBAAmB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,WAW1B,aAAa;AAAA;AAYnB,SAAS,mBAA2B;AAChC,QAAM,UAAU,QAAQ,IAAI;AAC5B,MAAI,WAAW,QAAQ,KAAK,EAAE,SAAS,GAAG;AACtC,UAAM,UAAU,QAAQ,KAAK;AAC7B,QAAI,QAAQ,WAAW,IAAI,GAAG;AAC1B,aAAO,KAAK,QAAQ,GAAG,QAAQ,MAAM,CAAC,CAAC;AAAA,IAC3C;AACA,QAAI,YAAY,KAAK;AACjB,aAAO,QAAQ;AAAA,IACnB;AACA,WAAO;AAAA,EACX;AACA,SAAO,KAAK,QAAQ,GAAG,QAAQ;AACnC;AACO,MAAM,aAAa,iBAAiB;AACpC,MAAM,oBAAoB,KAAK,YAAY,YAAY;AACvD,MAAM,gBAAgB,KAAK,YAAY,UAAU;AACjD,MAAM,kBAAkB,KAAK,YAAY,WAAW;AACpD,MAAM,mBAAmB,KAAK,iBAAiB,QAAQ;AACvD,MAAM,iBAAiB,KAAK,YAAY,MAAM;AAC9C,MAAM,mBAAmB,KAAK,YAAY,QAAQ;AAGlD,MAAM,YAAY,KAAK,iBAAiB,WAAW;AACnD,MAAM,UAAU,KAAK,iBAAiB,SAAS;AAC/C,MAAM,WAAW,KAAK,iBAAiB,UAAU;AACjD,MAAM,oBAAoB;AAC1B,MAAM,eAAe,KAAK,YAAY,cAAc;AACpD,MAAM,sBAAsB,KAAK,YAAY,sBAAsB;AACnE,MAAM,wBAAwB,KAAK,YAAY,aAAa;AAG5D,MAAM,qBAAqB,KAAK,YAAY,qBAAqB;AACjE,MAAM,uBAAuB,KAAK,YAAY,uBAAuB;AACrE,MAAM,yBAAyB,KAAK,YAAY,wBAAwB;AACxE,MAAM,aAAa,KAAK,YAAY,aAAa;AACjD,MAAM,wBAAwB,KAAK,YAAY,wBAAwB;AACvE,MAAM,uBAAuB,KAAK,YAAY,uBAAuB;AACrE,MAAM,oBAAoB,KAAK,YAAY,oBAAoB;AAG/D,MAAM,uBAAuB;AAC7B,MAAM,uBAAuB;AAC7B,MAAM,mBAAmB;AAGzB,MAAM,gBAAgB;AAItB,MAAM,qBAAqB;AAC3B,MAAM,sBAAsB;AAC5B,MAAM,uBAAuB;AAC7B,MAAM,qBAAqB,KAAK,KAAK;AAGrC,MAAM,uBAAuB;AAG7B,MAAM,wBAAkC,CAAC;AACzC,MAAM,uBAAiC,CAAC;","names":[]}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# TITAN — Harness Patterns
|
|
2
|
+
|
|
3
|
+
A self-audit of where TITAN stands against the agent-harness ecosystem catalogued in [`Picrew/awesome-agent-harness`](https://github.com/Picrew/awesome-agent-harness) and the principles in [12 Factor Agents](https://github.com/humanlayer/12-factor-agents).
|
|
4
|
+
|
|
5
|
+
The point of this doc is not marketing. It's a true table: what we have, what we partially have, what we explicitly don't have and why. It's updated each release.
|
|
6
|
+
|
|
7
|
+
> **Why this doc exists.** The recurring user pain in TITAN (token-budget hard-stops, identity hallucination, sycophancy, sparse model lists) all traced to harness-engineering gaps, not LLM problems. The article "[Skill Issue: Harness Engineering for Coding Agents](https://blog.langchain.com/skill-issue-harness-engineering-for-coding-agents/)" frames this directly: **harness quality drives agent quality**.
|
|
8
|
+
|
|
9
|
+
## Top-10 patterns (from the agent-harness research)
|
|
10
|
+
|
|
11
|
+
| # | Pattern | TITAN status | Where |
|
|
12
|
+
|---|---------|--------------|-------|
|
|
13
|
+
| 1 | **Explicit control loops + checkpointing** | ✅ Have | Shadow-git checkpoints (`src/agent/shadowGit.ts`), Command Post checkout/budgets/ancestry (`src/agent/commandPost.ts`), durable session persistence (`src/agent/session.ts`). |
|
|
14
|
+
| 2 | **Sandbox abstraction + isolation strategy** | ⚠️ Partial | Iframe sandbox for widgets (`ui/src/titan2/sandbox/SandboxRuntime.ts`), shell pre-exec scanner (`src/security/preExecScan.ts`), kill-switch (`src/safety/killSwitch.ts`). Missing: WASM/MicroVM-grade isolation. |
|
|
15
|
+
| 3 | **Context compaction + working-state management** | ✅ Have (fixed v5.7.0) | `buildSmartContext` + `compressContext` (`src/agent/contextManager.ts`, `src/agent/contextCompressor.ts`). v5.7.0 wired these into the budget-exceed path so `action: 'compress'` actually compresses instead of hard-stopping with "Session paused". |
|
|
16
|
+
| 4 | **Tool protocol standardisation via MCP** | ✅ Have | MCP Server (JSON-RPC 2.0, stdio + HTTP) in `src/mcp/`. Hindsight MCP bridge for cross-session episodic memory. |
|
|
17
|
+
| 5 | **Verification + evaluation as first-class** | ✅ Have | 11 live-eval suites in `src/eval/harness.ts`, 80 %-per-suite CI gate in `.github/workflows/eval-gate.yml`, `tests/unit/readme-claims.test.ts` for documentation truth. |
|
|
18
|
+
| 6 | **Multi-agent orchestration via explicit role boundaries** | ✅ Have | 43 personas in `assets/personas/`, Command Post governance, sub-agent registry. Tracks 7 + 9 of `docs/ROADMAP.md` strengthen this further. |
|
|
19
|
+
| 7 | **Gateway-level policy enforcement** | ✅ Have | Provider router with fail-fast (`isConfigured()`), circuit breakers, rate limiting, PII redaction, secret guard, pre-exec scanner. |
|
|
20
|
+
| 8 | **Deterministic workflow control (DAG / spec)** | ❌ Missing | TITAN runs autonomous loops. Spec-driven phases (Archon / GitHub Spec Kit style) are an open v6.x item. |
|
|
21
|
+
| 9 | **Approval delegation + human-in-the-loop** | ✅ Have | `src/skills/builtin/approval_gates.ts`, Command Post atomic checkout, classifier-backed safety pressure (Soma). |
|
|
22
|
+
| 10 | **Observability-native architecture** | ⚠️ Partial | PostHog telemetry, `~/.titan/bug-reports.jsonl`, audit log, internal `tracer.ts` + `diagnostics/otel.ts`. Missing: OpenInference / OpenTelemetry export so Langfuse / Phoenix / Helicone can consume traces directly. |
|
|
23
|
+
|
|
24
|
+
## 12 Factor Agents — TITAN compliance
|
|
25
|
+
|
|
26
|
+
Tracking [12 Factor Agents](https://github.com/humanlayer/12-factor-agents) by Dex Horthy / HumanLayer. Each factor + TITAN's stance:
|
|
27
|
+
|
|
28
|
+
| # | Factor | TITAN |
|
|
29
|
+
|---|--------|-------|
|
|
30
|
+
| 1 | Natural-language → structured tool calls | ✅ Tool router with provider-agnostic schema enforcement (`src/agent/toolRunner.ts`) |
|
|
31
|
+
| 2 | Own your prompts | ✅ `src/agent/systemPromptParts.ts` — composable blocks, per-mode assembly, per-model overlays |
|
|
32
|
+
| 3 | Own your context window | ✅ v5.7.0 — explicit `BudgetCheckResult` shape with compress/downgrade/stop actions, `buildSmartContext` machinery |
|
|
33
|
+
| 4 | Tools are just structured outputs | ✅ Zod schemas on every skill (`src/skills/registry.ts`) |
|
|
34
|
+
| 5 | Unify execution state + business state | ⚠️ Partial — sessions + agent state are unified; goals + cron live in adjacent stores |
|
|
35
|
+
| 6 | Launch / pause / resume with simple APIs | ✅ Session save/resume, checkpoint resume, kill-switch + restart |
|
|
36
|
+
| 7 | Contact humans with tool calls | ✅ Approval gates, command-post review queue, push notifications |
|
|
37
|
+
| 8 | Own your control flow | ✅ Explicit ReAct loop in `src/agent/agentLoop.ts`, not hidden inside a framework |
|
|
38
|
+
| 9 | Compact errors into context window | ✅ Error taxonomy in `src/providers/errorTaxonomy.ts`, structured error injection |
|
|
39
|
+
| 10 | Small, focused agents | ✅ Per-persona scope; v6.0 track #6 (agent-authored live widgets) strengthens further |
|
|
40
|
+
| 11 | Trigger from anywhere, meet users where they are | ✅ 19 channel adapters in `src/channels/` |
|
|
41
|
+
| 12 | Make your agent a stateless reducer | ⚠️ Partial — sessions are stateful; the per-turn function is pure modulo memory injection |
|
|
42
|
+
|
|
43
|
+
## Patterns we explicitly DON'T have (and why)
|
|
44
|
+
|
|
45
|
+
- **Deterministic DAG workflow builder** (LangGraph-style). The roadmap explicitly rejects this — drawn flows lose against agentic flows. We bet on the LLM as the planner, not the human-as-planner-of-flows.
|
|
46
|
+
- **WASM / MicroVM-grade execution sandbox**. Operational overhead vs the population we serve (single-user homelab + small-team SaaS) doesn't justify it yet.
|
|
47
|
+
- **A locked-in cloud control plane**. Cloud features are opt-in. TITAN must work fully offline against local Ollama — that's a hard line.
|
|
48
|
+
|
|
49
|
+
## What v5.7.0 specifically fixes from the harness research
|
|
50
|
+
|
|
51
|
+
Real user incident, 2026-05-10:
|
|
52
|
+
> 5-turn conversation → "Token budget exceeded (216,713/200,000). Session paused to control costs."
|
|
53
|
+
|
|
54
|
+
Root cause traced via the research: TITAN had `action: 'compress'` as the default budget action, BUT `promptBudget.checkBudget()` returned a single string regardless of action, and `agentLoop.ts` treated any non-null result as a hard stop. The compression machinery existed (`buildSmartContext`); nothing called it on budget exceed.
|
|
55
|
+
|
|
56
|
+
v5.7.0 fix:
|
|
57
|
+
1. **`promptBudget.checkBudget()` returns a structured `BudgetCheckResult`** with `action: 'compress' | 'downgrade' | 'stop'`. Compresses by default.
|
|
58
|
+
2. **`agentLoop.ts` honors `action: 'compress'`** — invokes `buildSmartContext`, swaps the message history, resets the budget counter, continues the loop.
|
|
59
|
+
3. **The user-facing message** for the compress path no longer says "Session paused" — it says the context was trimmed and the session is continuing.
|
|
60
|
+
4. **New unit tests** in `tests/unit/promptBudget-compress.test.ts` pin the structured return shape and the message wording, so the regression cannot silently recur.
|
|
61
|
+
|
|
62
|
+
References used:
|
|
63
|
+
- Anthropic — "Effective context engineering for AI agents" — context-as-bottleneck
|
|
64
|
+
- 12 Factor Agents — §3 ("Own your context window") + §10 ("Small, focused agents")
|
|
65
|
+
- awesome-agent-harness top-10 pattern #3
|
|
66
|
+
|
|
67
|
+
## How to verify
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Unit tests
|
|
71
|
+
npx vitest run tests/unit/promptBudget-compress.test.ts
|
|
72
|
+
|
|
73
|
+
# Live in a session — set a low maxTokens to force the compress branch
|
|
74
|
+
TITAN_MAX_TOKENS=2000 npm run dev:gateway
|
|
75
|
+
# Then chat normally; after a few turns the log will say:
|
|
76
|
+
# [Budget] Compressed context: 12 → 4 messages, budget reset
|
|
77
|
+
# instead of "Session paused to control costs."
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Reading list (from the research)
|
|
81
|
+
|
|
82
|
+
- [12 Factor Agents](https://github.com/humanlayer/12-factor-agents) — Dex Horthy / HumanLayer
|
|
83
|
+
- [Anatomy of an Agent Harness](https://www.harnessengineering.com/anatomy-of-a-harness/) — Martin Fowler series
|
|
84
|
+
- [Skill Issue: Harness Engineering for Coding Agents](https://blog.langchain.com/skill-issue-harness-engineering-for-coding-agents/)
|
|
85
|
+
- [Effective context engineering for AI agents](https://www.anthropic.com/news/context-engineering) — Anthropic
|
|
86
|
+
- [Building Effective AI Agents](https://www.anthropic.com/research/building-effective-agents) — Anthropic
|
|
87
|
+
- [Your Agent Needs a Harness, Not a Framework](https://blog.langchain.com/your-agent-needs-a-harness-not-a-framework/)
|
|
88
|
+
- [Writing effective tools for AI agents](https://www.anthropic.com/news/writing-effective-tools-for-ai-agents-using-ai-agents) — Anthropic
|
|
89
|
+
- [Picrew/awesome-agent-harness](https://github.com/Picrew/awesome-agent-harness) — the full catalogue
|
|
90
|
+
- [addyosmani/agent-skills](https://github.com/addyosmani/agent-skills) — TITAN ships these in `assets/agent-skills/`
|
|
91
|
+
|
|
92
|
+
If you see a pattern in the awesome-agent-harness catalogue that TITAN should adopt, open an issue on the [TITAN repo](https://github.com/Djtony707/TITAN/issues) with the pattern name + a one-line argument for fit.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "titan-agent",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.7.1",
|
|
4
4
|
"description": "TITAN — Autonomous AI agent framework with self-improvement, multi-agent orchestration, 36 LLM providers, 16 channel adapters, GPU VRAM management, mesh networking, LiveKit voice, TITAN-Soma homeostatic drives, and a React Mission Control dashboard. Open-source, TypeScript, MIT licensed.",
|
|
5
5
|
"author": "Tony Elliott (https://github.com/Djtony707)",
|
|
6
6
|
"repository": {
|
package/ui/dist/sw.js
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* but a default falls back to the source-controlled value here.
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
|
-
const CACHE_NAME = 'titan-' + ('
|
|
23
|
+
const CACHE_NAME = 'titan-' + ('1778478282544');
|
|
24
24
|
const ASSETS_PREFIX = '/assets/';
|
|
25
25
|
|
|
26
26
|
self.addEventListener('install', (event) => {
|