Vapora/docs/adrs/0014-learning-profiles.html
Jesús Pérez 7110ffeea2
Some checks failed
Rust CI / Security Audit (push) Has been cancelled
Rust CI / Check + Test + Lint (nightly) (push) Has been cancelled
Rust CI / Check + Test + Lint (stable) (push) Has been cancelled
chore: extend doc: adr, tutorials, operations, etc
2026-01-12 03:32:47 +00:00

478 lines
21 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE HTML>
<html lang="en" class="light sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>0014: Learning Profiles - VAPORA Platform Documentation</title>
<!-- Custom HTML head -->
<meta name="description" content="Comprehensive documentation for VAPORA, an intelligent development orchestration platform built entirely in Rust.">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="../favicon.svg">
<link rel="shortcut icon" href="../favicon.png">
<link rel="stylesheet" href="../css/variables.css">
<link rel="stylesheet" href="../css/general.css">
<link rel="stylesheet" href="../css/chrome.css">
<link rel="stylesheet" href="../css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="../fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" id="highlight-css" href="../highlight.css">
<link rel="stylesheet" id="tomorrow-night-css" href="../tomorrow-night.css">
<link rel="stylesheet" id="ayu-highlight-css" href="../ayu-highlight.css">
<!-- Custom theme stylesheets -->
<!-- Provide site root and default themes to javascript -->
<script>
const path_to_root = "../";
const default_light_theme = "light";
const default_dark_theme = "dark";
</script>
<!-- Start loading toc.js asap -->
<script src="../toc.js"></script>
</head>
<body>
<div id="mdbook-help-container">
<div id="mdbook-help-popup">
<h2 class="mdbook-help-title">Keyboard shortcuts</h2>
<div>
<p>Press <kbd></kbd> or <kbd></kbd> to navigate between chapters</p>
<p>Press <kbd>S</kbd> or <kbd>/</kbd> to search in the book</p>
<p>Press <kbd>?</kbd> to show this help</p>
<p>Press <kbd>Esc</kbd> to hide this help</p>
</div>
</div>
</div>
<div id="body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
let theme = localStorage.getItem('mdbook-theme');
let sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
const default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? default_dark_theme : default_light_theme;
let theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('light')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
let sidebar = null;
const sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="default_theme">Auto</button></li>
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search (`/`)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="/ s" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">VAPORA Platform Documentation</h1>
<div class="right-buttons">
<a href="../print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
<a href="https://github.com/vapora-platform/vapora" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
<a href="https://github.com/vapora-platform/vapora/edit/main/docs/src/../adrs/0014-learning-profiles.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="adr-014-learning-profiles-con-recency-bias"><a class="header" href="#adr-014-learning-profiles-con-recency-bias">ADR-014: Learning Profiles con Recency Bias</a></h1>
<p><strong>Status</strong>: Accepted | Implemented
<strong>Date</strong>: 2024-11-01
<strong>Deciders</strong>: Agent Architecture Team
<strong>Technical Story</strong>: Tracking per-task-type agent expertise with recency-weighted learning</p>
<hr />
<h2 id="decision"><a class="header" href="#decision">Decision</a></h2>
<p>Implementar <strong>Learning Profiles per-task-type con exponential recency bias</strong> para adaptar selección de agentes a capacidad actual.</p>
<hr />
<h2 id="rationale"><a class="header" href="#rationale">Rationale</a></h2>
<ol>
<li><strong>Recency Bias</strong>: Últimos 7 días pesados 3× más alto (agentes mejoran rápidamente)</li>
<li><strong>Per-Task-Type</strong>: Un perfil por tipo de tarea (architecture vs code gen vs review)</li>
<li><strong>Avoid Stale Data</strong>: No usar promedio histórico (puede estar desactualizado)</li>
<li><strong>Confidence Score</strong>: Requiere 20+ ejecuciones antes de confianza completa</li>
</ol>
<hr />
<h2 id="alternatives-considered"><a class="header" href="#alternatives-considered">Alternatives Considered</a></h2>
<h3 id="-simple-average-all-time"><a class="header" href="#-simple-average-all-time">❌ Simple Average (All-Time)</a></h3>
<ul>
<li><strong>Pros</strong>: Simple</li>
<li><strong>Cons</strong>: Histórico antiguo distorsiona, no adapta a mejoras actuales</li>
</ul>
<h3 id="-sliding-window-last-n-executions"><a class="header" href="#-sliding-window-last-n-executions">❌ Sliding Window (Last N Executions)</a></h3>
<ul>
<li><strong>Pros</strong>: More recent data</li>
<li><strong>Cons</strong>: Artificial cutoff, perder contexto histórico</li>
</ul>
<h3 id="-exponential-recency-bias-chosen"><a class="header" href="#-exponential-recency-bias-chosen">✅ Exponential Recency Bias (CHOSEN)</a></h3>
<ul>
<li>Pesa natural según antigüedad, mejor refleja capacidad actual</li>
</ul>
<hr />
<h2 id="trade-offs"><a class="header" href="#trade-offs">Trade-offs</a></h2>
<p><strong>Pros</strong>:</p>
<ul>
<li>✅ Adapts to agent capability improvements quickly</li>
<li>✅ Exponential decay is mathematically sound</li>
<li>✅ 20+ execution confidence threshold prevents overfitting</li>
<li>✅ Per-task-type specialization</li>
</ul>
<p><strong>Cons</strong>:</p>
<ul>
<li>⚠️ Cold-start: new agents start with low confidence</li>
<li>⚠️ Requires 20 executions to reach full confidence</li>
<li>⚠️ Storage overhead (per agent × per task type)</li>
</ul>
<hr />
<h2 id="implementation"><a class="header" href="#implementation">Implementation</a></h2>
<p><strong>Learning Profile Model</strong>:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>// crates/vapora-agents/src/learning_profile.rs
pub struct TaskTypeLearning {
pub agent_id: String,
pub task_type: String,
pub executions_total: u32,
pub executions_successful: u32,
pub avg_quality_score: f32,
pub avg_latency_ms: f32,
pub last_updated: DateTime&lt;Utc&gt;,
pub records: Vec&lt;ExecutionRecord&gt;, // Last 100 executions
}
impl TaskTypeLearning {
/// Recency weight formula: 3.0 * e^(-days_ago / 7.0) for recent
/// Then e^(-days_ago / 7.0) for older
pub fn compute_recency_weight(days_ago: f64) -&gt; f64 {
if days_ago &lt;= 7.0 {
3.0 * (-days_ago / 7.0).exp() // 3× weight for last week
} else {
(-days_ago / 7.0).exp() // Exponential decay after
}
}
/// Weighted expertise score (0.0 - 1.0)
pub fn expertise_score(&amp;self) -&gt; f32 {
if self.executions_total == 0 {
return 0.0;
}
let now = Utc::now();
let weighted_sum: f64 = self.records
.iter()
.map(|r| {
let days_ago = (now - r.timestamp).num_days() as f64;
let weight = Self::compute_recency_weight(days_ago);
(r.quality_score as f64) * weight
})
.sum();
let weight_sum: f64 = self.records
.iter()
.map(|r| {
let days_ago = (now - r.timestamp).num_days() as f64;
Self::compute_recency_weight(days_ago)
})
.sum();
(weighted_sum / weight_sum) as f32
}
/// Confidence score: min(1.0, executions / 20)
pub fn confidence(&amp;self) -&gt; f32 {
std::cmp::min(1.0, (self.executions_total as f32) / 20.0)
}
/// Final score combines expertise × confidence
pub fn score(&amp;self) -&gt; f32 {
self.expertise_score() * self.confidence()
}
}
<span class="boring">}</span></code></pre></pre>
<p><strong>Recording Execution</strong>:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub async fn record_execution(
db: &amp;Surreal&lt;Ws&gt;,
agent_id: &amp;str,
task_type: &amp;str,
success: bool,
quality: f32,
) -&gt; Result&lt;()&gt; {
let record = ExecutionRecord {
agent_id: agent_id.to_string(),
task_type: task_type.to_string(),
success,
quality_score: quality,
timestamp: Utc::now(),
};
// Store in KG
db.create("executions").content(&amp;record).await?;
// Update learning profile
let profile = db.query(
"SELECT * FROM task_type_learning \
WHERE agent_id = $1 AND task_type = $2"
)
.bind((agent_id, task_type))
.await?;
// Update counters (incremental)
// If new profile, create with initial values
Ok(())
}
<span class="boring">}</span></code></pre></pre>
<p><strong>Agent Selection Using Profiles</strong>:</p>
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
</span><span class="boring">fn main() {
</span>pub async fn select_agent_for_task(
db: &amp;Surreal&lt;Ws&gt;,
task_type: &amp;str,
) -&gt; Result&lt;AgentId&gt; {
let profiles = db.query(
"SELECT agent_id, expertise_score(), confidence(), score() \
FROM task_type_learning \
WHERE task_type = $1 \
ORDER BY score() DESC \
LIMIT 1"
)
.bind(task_type)
.await?;
let best_agent = profiles
.take::&lt;TaskTypeLearning&gt;(0)?
.ok_or(Error::NoAgentsAvailable)?;
Ok(best_agent.agent_id)
}
<span class="boring">}</span></code></pre></pre>
<p><strong>Scoring Formula</strong>:</p>
<pre><code>expertise_score = Σ(quality_score_i × recency_weight_i) / Σ(recency_weight_i)
recency_weight_i = {
3.0 × e^(-days_ago / 7.0) if days_ago ≤ 7 days (3× recent bias)
e^(-days_ago / 7.0) if days_ago &gt; 7 days (exponential decay)
}
confidence = min(1.0, total_executions / 20)
final_score = expertise_score × confidence
</code></pre>
<p><strong>Key Files</strong>:</p>
<ul>
<li><code>/crates/vapora-agents/src/learning_profile.rs</code> (profile computation)</li>
<li><code>/crates/vapora-agents/src/scoring.rs</code> (score calculations)</li>
<li><code>/crates/vapora-agents/src/selector.rs</code> (agent selection logic)</li>
</ul>
<hr />
<h2 id="verification"><a class="header" href="#verification">Verification</a></h2>
<pre><code class="language-bash"># Test recency weight calculation
cargo test -p vapora-agents test_recency_weight
# Test expertise score with mixed recent/old executions
cargo test -p vapora-agents test_expertise_score
# Test confidence with &lt;20 and &gt;20 executions
cargo test -p vapora-agents test_confidence_score
# Integration: record executions and verify profile updates
cargo test -p vapora-agents test_profile_recording
# Integration: select best agent using profiles
cargo test -p vapora-agents test_agent_selection_by_profile
# Verify cold-start (new agent has low score)
cargo test -p vapora-agents test_cold_start_bias
</code></pre>
<p><strong>Expected Output</strong>:</p>
<ul>
<li>Recent executions (&lt; 7 days) weighted 3× higher</li>
<li>Older executions gradually decay exponentially</li>
<li>New agents (&lt; 20 executions) have lower confidence</li>
<li>Agents with 20+ executions reach full confidence</li>
<li>Best agent selected based on recency-weighted score</li>
<li>Profile updates recorded in KG</li>
</ul>
<hr />
<h2 id="consequences"><a class="header" href="#consequences">Consequences</a></h2>
<h3 id="agent-dynamics"><a class="header" href="#agent-dynamics">Agent Dynamics</a></h3>
<ul>
<li>Agents that improve rapidly rise in selection order</li>
<li>Poor-performing agents decline even with historical success</li>
<li>Learning profiles encourage agent improvement (recent success rewarded)</li>
</ul>
<h3 id="data-management"><a class="header" href="#data-management">Data Management</a></h3>
<ul>
<li>One profile per agent × per task type</li>
<li>Last 100 executions per profile retained (rest in archive)</li>
<li>Storage: ~50KB per profile</li>
</ul>
<h3 id="monitoring"><a class="header" href="#monitoring">Monitoring</a></h3>
<ul>
<li>Track which agents are trending up/down</li>
<li>Identify agents with cold-start problem</li>
<li>Alert if all agents for task type below threshold</li>
</ul>
<h3 id="user-experience"><a class="header" href="#user-experience">User Experience</a></h3>
<ul>
<li>Best agents selected automatically</li>
<li>Selection adapts to agent improvements</li>
<li>Users see faster task completion over time</li>
</ul>
<hr />
<h2 id="references"><a class="header" href="#references">References</a></h2>
<ul>
<li><code>/crates/vapora-agents/src/learning_profile.rs</code> (profile implementation)</li>
<li><code>/crates/vapora-agents/src/scoring.rs</code> (scoring logic)</li>
<li>ADR-013 (Knowledge Graph Temporal)</li>
<li>ADR-017 (Confidence Weighting)</li>
</ul>
<hr />
<p><strong>Related ADRs</strong>: ADR-013 (Knowledge Graph), ADR-017 (Confidence), ADR-018 (Load Balancing)</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../../adrs/0013-knowledge-graph.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../../adrs/0015-budget-enforcement.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../../adrs/0013-knowledge-graph.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../../adrs/0015-budget-enforcement.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script>
window.playground_copyable = true;
</script>
<script src="../elasticlunr.min.js"></script>
<script src="../mark.min.js"></script>
<script src="../searcher.js"></script>
<script src="../clipboard.min.js"></script>
<script src="../highlight.js"></script>
<script src="../book.js"></script>
<!-- Custom JS scripts -->
</div>
</body>
</html>