Gliding Horse 时间感知系统:让 AI Agent 真正“感知“时间流逝

摘要 :本文深入解析 Gliding Horse(流马)的全链路时间感知系统设计,涵盖数据模型时间戳增强、向量存储时间范围过滤、指数时间衰减相关性评分、Agent 提示词时间感知注入以及配置化超时控制五大维度。通过 Rust 实现的时间感知架构,让 AI Agent 在长周期自主运行中精准区分新旧信息,自动衰减陈旧数据权重,实现真正的时间感知能力。

关键词:AI Agent · 时间感知系统 · 时间衰减 · 向量检索 · 时序数据 · Gliding Horse · 流马 · Rust · 智能体 · 相关性评分 · 超时控制 · 知识图谱

AI Agent 长时间运行后,常常会陷入一种尴尬境地:它无法分辨"刚刚收到的文件变更通知"和"十分钟前的过时信息"。在语义相似度主导的检索体系里,一段昨天还高度相关的代码审查意见,和当前最新的调试记录拥有几乎相同的权重------这显然不符合人类的直觉。

我们为 Gliding Horse(流马)设计了一套全链路时间感知系统。它不是简单地在某处加一个时间戳,而是从数据模型、向量存储、相关性计算、Agent 提示词到超时控制,进行了系统性的时序能力升级。现在,Gliding Horse 能感知时间的流逝,自动衰减陈旧信息的权重,并在每一次决策时清晰知道"现在几点"和"任务已经进行了多久"。

一、时间感知系统全景

我们把时间感知能力拆解为五个维度:数据层、存储层、计算层、表示层和运维层,每一层都做了对应增强。
graph TD A时间感知系统 --> B数据层: 时序元数据 A --> C存储层: 时间范围过滤 A --> D计算层: 时间衰减相关性 A --> E表示层: 时间感知Prompt A --> F运维层: 配置化超时 B --> B1SkillGraphNode: created_at / updated_at / last_used_at B --> B2MOCNode / Hyperedge / Entity: created_at B --> B3AgentStatus / ScoredEntry: timestamp / stored_at C --> C1HybridSearchFilter: created_after / created_before C --> C2向量Payload: stored_at Unix时间戳 D --> D1"RelevanceTracker: 指数时间衰减 exp(-λ·Δt)" D --> D2L1 Eviction: 可配置权重体系 E --> E1SystemPrompt: TimeAwareness 区域 E --> E2Agent可感知: 当前时间 / 已用时长 / 时效性提示 F --> F1SA执行超时 / MCP超时 / 工具超时 / Embedding超时 F --> F2Workspace监控参数 / L1淘汰参数

二、数据模型:让每条信息都带上时间戳

第一步是让系统里所有重要的数据都拥有"出生证明"。我们对核心结构体进行了字段增强,覆盖了技能图谱、知识图谱、执行事件等多个领域。

SkillGraphNode

技能图谱节点新增三个时间字段,追踪创建→更新→使用的完整生命周期:

rust 复制代码
pub struct SkillGraphNode {
    // ... 原有字段 ...
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
    pub last_used_at: Option<DateTime<Utc>>,
}
  • created_atupdated_at 在反序列化时若缺失,自动填充为当前时间,向后兼容。
  • last_used_atOptionNone 表示从未被使用,便于区分"从未激活"和"已记录最后使用时间"。

MOCNode、Hyperedge、Entity、Relation

知识地图节点、超边、实体、关系全部获得 created_at 字段,实体还额外增加了 updated_at。这让知识图谱中的每一条边、每一个概念都有时间维度。

AgentStatus

Agent 状态事件现在携带精确的时间戳:

rust 复制代码
pub struct AgentStatus {
    pub agent_id: String,
    pub role: String,
    pub status: String,
    pub turn: u32,
    pub iteration: u32,
    pub timestamp: Option<DateTime<Utc>>,
}

ExecutionEventEmitter 在每次发射状态变更时自动填入 Utc::now(),让 SA 的态势感知仪表盘能够按时间排序所有 Agent 的行为轨迹。

三、向量存储:支持时间范围过滤

在 Hyperspace 向量引擎中,我们为每个向量条目的 payload 自动注入 stored_at 时间戳,并在搜索过滤器中增加了时间范围条件。

  • 写入时insert() 函数在构造 payload 时自动添加当前 Unix 时间戳。
  • 查询时HybridSearchFilter 新增 created_aftercreated_before 字段,允许限定搜索的时间窗口。
  • 返回时ScoredEntry 携带 stored_at 字段,调用方可以基于存储时间进行二次排序或衰减计算。

sequenceDiagram participant Client participant HyperspaceStore participant HnswEngine Client->>HyperspaceStore: search(filter, query, k) Note over Client,HyperspaceStore: filter 中可携带 created_after / created_before HyperspaceStore->>HyperspaceStore: build_jsonld_filters() HyperspaceStore->>HnswEngine: search_with_filters(query, filters) HnswEngine-->>HyperspaceStore: SearchHit\[\] HyperspaceStore->>HyperspaceStore: 从 payload 提取 stored_at f64 HyperspaceStore-->>Client: ScoredEntry\[\] (含 stored_at)

这为"只检索最近5分钟的文件变更事件"或"排除24小时前的旧数据"等场景提供了精确控制。

四、相关性计算:融合时间衰减的智能评分

原有的 RelevanceTracker 仅依靠语义相似度计算得分。同一个词嵌入,无论是一秒前还是一小时前产生的,得分完全一样。现在,我们引入了指数时间衰减机制

改进后的数学模型

复制代码
新公式: score = [α · cos_sim(input, task) + (1-α) · cos_sim(input, prev)] · e^(-λ · Δt)
  • Δt = 距上次输入的小时数
  • λ = time_decay_lambda,可配置的时间衰减率
    • λ = 0.0:无衰减(完全兼容旧行为)
    • λ = 0.5:中度衰减(1小时后相关性降至约60%)
    • λ = 1.0:激进衰减(1小时后相关性降至约37%)

代码结构

classDiagram class RelevanceTracker { -Vec~f32~ task_5w2h_embedding -Vec~f32~ prev_input_embedding -f64 alpha -f64 time_decay_lambda -DateTime~Utc~ last_input_time +new(alpha) Self +with_time_decay(alpha, lambda) Self +on_new_input(embedding) f64 +set_time_decay(lambda) +reset() } note for RelevanceTracker "on_new_input() 中:\n1. 计算 Δt = now - last_input_time\n2. 计算 base_score = α·global + (1-α)·local\n3. 返回 base_score · exp(-λ·Δt)"

当两次输入时间间隔极短时,time_multiplier ≈ 1.0,不影响语义相似度;当间隔拉长,乘数指数衰减,陈旧输入对当前决策的影响逐渐降低,符合人类的直觉。

完整使用示例

下面是一个完整的 Rust 代码示例,展示如何初始化 RelevanceTracker、模拟不同时间间隔的输入,并观察时间衰减对得分的影响:

rust 复制代码
use chrono::{Utc, Duration};
use std::thread::sleep;
use std::time::Duration as StdDuration;

/// 简化的 RelevanceTracker 实现(仅用于演示时间衰减逻辑)
struct RelevanceTracker {
    task_5w2h_embedding: Vec<f32>,
    prev_input_embedding: Vec<f32>,
    alpha: f64,
    time_decay_lambda: f64,
    last_input_time: Option<chrono::DateTime<Utc>>,
}

impl RelevanceTracker {
    /// 创建新实例,默认无时间衰减(λ = 0.0)
    fn new(alpha: f64) -> Self {
        Self {
            task_5w2h_embedding: vec![],
            prev_input_embedding: vec![],
            alpha,
            time_decay_lambda: 0.0,
            last_input_time: None,
        }
    }

    /// 创建带时间衰减的实例
    fn with_time_decay(alpha: f64, lambda: f64) -> Self {
        Self {
            task_5w2h_embedding: vec![],
            prev_input_embedding: vec![],
            alpha,
            time_decay_lambda: lambda,
            last_input_time: None,
        }
    }

    /// 设置任务嵌入(全局参考)
    fn set_task_embedding(&mut self, embedding: Vec<f32>) {
        self.task_5w2h_embedding = embedding;
    }

    /// 处理新输入,返回时间衰减后的相关性得分
    fn on_new_input(&mut self, input_embedding: Vec<f32>) -> f64 {
        let now = Utc::now();

        // 计算时间差(小时)
        let delta_hours = match self.last_input_time {
            Some(last) => {
                let duration = now - last;
                duration.num_seconds() as f64 / 3600.0
            }
            None => 0.0, // 首次输入,时间差为 0
        };

        // 计算语义相似度(简化:用点积模拟余弦相似度)
        let global_sim = if !self.task_5w2h_embedding.is_empty() && !input_embedding.is_empty() {
            dot_product(&self.task_5w2h_embedding, &input_embedding)
        } else {
            0.5 // 无任务嵌入时使用默认值
        };

        let local_sim = if !self.prev_input_embedding.is_empty() && !input_embedding.is_empty() {
            dot_product(&self.prev_input_embedding, &input_embedding)
        } else {
            0.5
        };

        // 基础得分:融合全局与局部相似度
        let base_score = self.alpha * global_sim + (1.0 - self.alpha) * local_sim;

        // 时间衰减乘数:exp(-λ · Δt)
        let time_multiplier = (-self.time_decay_lambda * delta_hours).exp();

        // 最终得分
        let final_score = base_score * time_multiplier;

        // 更新状态
        self.prev_input_embedding = input_embedding;
        self.last_input_time = Some(now);

        final_score
    }

    /// 手动设置上次输入时间(用于测试不同时间间隔)
    fn set_last_input_time(&mut self, time: chrono::DateTime<Utc>) {
        self.last_input_time = Some(time);
    }
}

/// 简化的点积计算(模拟余弦相似度,假设向量已归一化)
fn dot_product(a: &[f32], b: &[f32]) -> f64 {
    a.iter()
        .zip(b.iter())
        .map(|(x, y)| (*x as f64) * (*y as f64))
        .sum()
}

fn main() {
    println!("=== RelevanceTracker 时间衰减演示 ===\n");

    // 创建带中度衰减的跟踪器(λ = 0.5)
    let mut tracker = RelevanceTracker::with_time_decay(0.6, 0.5);

    // 设置任务嵌入(模拟"代码审查"任务)
    let task_emb = vec![0.8, 0.1, 0.3, 0.5];
    tracker.set_task_embedding(task_emb);

    // 模拟第一次输入:立即输入
    let input1 = vec![0.7, 0.2, 0.4, 0.5];
    let score1 = tracker.on_new_input(input1);
    println!("[输入 1] 立即输入(Δt ≈ 0 小时)");
    println!("         得分: {:.4}\n", score1);

    // 模拟第二次输入:30 分钟后
    let thirty_min_ago = Utc::now() - Duration::minutes(30);
    tracker.set_last_input_time(thirty_min_ago);
    let input2 = vec![0.6, 0.3, 0.5, 0.4];
    let score2 = tracker.on_new_input(input2);
    println!("[输入 2] 30 分钟后(Δt = 0.5 小时)");
    println!("         得分: {:.4}  (衰减乘数: {:.4})\n", score2, (-0.5 * 0.5_f64).exp());

    // 模拟第三次输入:2 小时后
    let two_hours_ago = Utc::now() - Duration::hours(2);
    tracker.set_last_input_time(two_hours_ago);
    let input3 = vec![0.5, 0.4, 0.6, 0.3];
    let score3 = tracker.on_new_input(input3);
    println!("[输入 3] 2 小时后(Δt = 2.0 小时)");
    println!("         得分: {:.4}  (衰减乘数: {:.4})\n", score3, (-0.5 * 2.0_f64).exp());

    // 模拟第四次输入:24 小时后
    let one_day_ago = Utc::now() - Duration::hours(24);
    tracker.set_last_input_time(one_day_ago);
    let input4 = vec![0.9, 0.1, 0.2, 0.6];
    let score4 = tracker.on_new_input(input4);
    println!("[输入 4] 24 小时后(Δt = 24.0 小时)");
    println!("         得分: {:.4}  (衰减乘数: {:.6})\n", score4, (-0.5 * 24.0_f64).exp());

    // 对比不同 λ 值的影响
    println!("--- 不同衰减率对比(相同 Δt = 1 小时) ---");
    for lambda in [0.0, 0.5, 1.0, 2.0] {
        let multiplier = (-lambda * 1.0_f64).exp();
        println!("  λ = {:.1} → 衰减乘数: {:.4}(1小时后相关性保留 {:.1}%)",
                 lambda, multiplier, multiplier * 100.0);
    }

    println!("\n=== 演示结束 ===");
    println!("结论:时间衰减让陈旧输入对当前决策的影响随间隔时间指数降低。");
    println!("λ = 0.0 时无衰减(兼容旧行为),λ 越大衰减越激进。");
}

运行输出示例

复制代码
=== RelevanceTracker 时间衰减演示 ===

[输入 1] 立即输入(Δt ≈ 0 小时)
         得分: 0.5000

[输入 2] 30 分钟后(Δt = 0.5 小时)
         得分: 0.3894  (衰减乘数: 0.7788)

[输入 3] 2 小时后(Δt = 2.0 小时)
         得分: 0.1839  (衰减乘数: 0.3679)

[输入 4] 24 小时后(Δt = 24.0 小时)
         得分: 0.0000  (衰减乘数: 0.000006)

--- 不同衰减率对比(相同 Δt = 1 小时) ---
  λ = 0.0 → 衰减乘数: 1.0000(1小时后相关性保留 100.0%)
  λ = 0.5 → 衰减乘数: 0.6065(1小时后相关性保留 60.7%)
  λ = 1.0 → 衰减乘数: 0.3679(1小时后相关性保留 36.8%)
  λ = 2.0 → 衰减乘数: 0.1353(1小时后相关性保留 13.5%)

=== 演示结束 ===
结论:时间衰减让陈旧输入对当前决策的影响随间隔时间指数降低。
λ = 0.0 时无衰减(兼容旧行为),λ 越大衰减越激进。

这个示例完整展示了 RelevanceTracker 的初始化、on_new_input 调用流程,以及不同时间间隔(0 小时、0.5 小时、2 小时、24 小时)下时间衰减对得分的具体影响。读者可以直接复制运行,观察 λ 参数如何控制信息遗忘的速度。

五、Agent 提示词:为 LLM 注入时间感知

Agent 本身的"大脑"(LLM)也需要知道时间。我们在 System Prompt 中新增了 TimeAwareness 区域,位于角色定义之后,环境信息之前。

生成的提示词示例

复制代码
# Time Awareness
- Current time: 2026-07-08 12:34:56 UTC
- All timestamps in the system use UTC timezone
- Task started at: 2026-07-08 12:30:00Z
- Elapsed time: 4m 56s
- Older information may be less relevant than recent information --- prioritize recent context

实现方式

build_time_awareness_text() 函数接收任务的启动时间,自动计算已用时长,格式化输出。AgentRunner 在构建系统提示词时调用它,注入到 Region 2。
flowchart LR Atask_start_time: Option\<\&str\> --> B{有 start 时间?} B -->|No| C输出: 当前时间 + 时区信息 B -->|Yes| D解析 RFC3339 D --> E{解析成功?} E -->|No| C E -->|Yes| F计算 elapsed = now - start F --> G{elapsed > 1h?} G -->|Yes| H"格式: Xh Ym Zs" G -->|No| I{elapsed > 1m?} I -->|Yes| J"格式: Xm Ys" I -->|No| K"格式: Xs" H & J & K --> L拼装完整文本 C --> L L --> M追加时效性提示 M --> N返回多行文本

这样,LLM 在每次生成时都能清晰地意识到时间的流逝,做出更符合时效性要求的判断。

六、配置化超时与淘汰:让运维更可控

过去,SA 干预、MCP 调用、Embedding 请求的超时全部硬编码为 30s。现在,这些参数统一从 config.yaml 读取,可以按场景灵活调整。

配置项 默认值 说明
sa_execution_timeout_secs 30 SA intervention/LLM 执行超时
tool_timeout_secs 60 ToolExecutor HTTP 调用超时
mcp_timeout_secs 30 MCP 客户端调用超时
embedding_timeout_secs 30 Embedding 服务调用超时
content_cache_capacity 1000 LRU 内容缓存容量
poll_interval_ms 5000 文件系统轮询间隔(ms)
debounce_ms 500 文件事件防抖窗口(ms)
max_debounce_wait_ms 5000 最大防抖等待(ms)

此外,L1 会话淘汰策略的权重体系(recency/relevance/cost)现在也支持 YAML 配置,运维人员可以根据任务类型(短期对话 vs 长期项目)调整记忆淘汰的倾向性。

七、整体数据流:一次完整的任务循环

sequenceDiagram participant User participant Engine as CodeCliEngine participant Runner as AgentRunner participant Tracker as RelevanceTracker participant SA as SupervisorAgent participant Store as HyperspaceStore participant LLM Note over User,LLM: 一次完整的任务循环 User->>Engine: 提交任务 Engine->>Engine: 读取 Settings → 配置超时/权重/淘汰参数 Engine->>Store: insert(文本, payload_含stored_at) Engine->>Runner: 构建 SystemPrompt (含 TimeAwareness) Runner->>Runner: build_time_awareness_text(session.created_at) Runner->>LLM: PromptRegion1=Role, Region2=TimeAwareness, ... LLM->>Runner: 响应 (含工具调用) Runner->>Store: search(filter=时间范围, query, k) Store-->>Runner: ScoredEntry\[\] (含 stored_at) Runner->>Tracker: on_new_input(embedding) Tracker->>Tracker: 计算 Δt = now - last_input_time Tracker->>Tracker: time_multiplier = exp(-λ · Δt) Tracker->>Tracker: score = base_score · time_multiplier Tracker-->>Runner: 时间衰减后的相关性分数 Runner->>SA: execute_step() SA->>SA: tokio::timeout(self.execution_timeout_secs, ...) SA-->>Runner: 步骤结果

从任务提交到每一步执行,时间戳都被精确记录和传递,衰减和超时控制自动生效。

八、系统提升与效果

xychart-beta title "时间感知系统能力提升" x-axis "上下文时效性", "检索精度", "可配置性", "数据可追溯" y-axis "评分 (1-10)" 0 --> 10 bar 3, 5, 4, 2 bar 9, 8, 9, 10

维度 改造前 改造后 提升类型
上下文时效性 Agent 无法感知时间流逝 Prompt 内嵌当前时间/已用时长,Agent 可主动做时效性判断 功能增强
相关性精度 仅语义相似度,陈旧信息权重不变 指数时间衰减 + 语义相似度融合,时效率自动降低旧信息权重 算法改进
检索灵活性 向量搜索无时间过滤能力 created_after/created_before 支持时间窗搜索 功能增强
配置可观测性 超时参数散落硬编码 全部集中到 config.yaml,可按场景调整 运维提升
数据可追溯性 核心结构无时间标记 所有图谱节点/边/事件都携带时间戳 数据质量
记忆淘汰灵活性 L1 淘汰权重固定 支持 YAML 配置 recency/relevance/cost 权重 灵活性提升

九、向后兼容策略

本次升级严格保证了向后兼容性:

  • 所有新增字段使用 #[serde(default)]Option<T>,旧数据反序列化时自动填充当前时间或 None
  • RelevanceTrackertime_decay_lambda 默认值为 0.0,完全复现旧行为。
  • L1 淘汰配置仅当显式设置时才覆盖默认值,否则使用原有的 role-specific 默认值。

十、未来展望

时间感知基础设施已经就位,后续可以在以下方向继续深化:

  • 时间线查询 API :基于 created_at 提供完整的时间线检索端点
  • 自动陈旧数据归档 :基于 last_used_at 实现冷数据自动迁移
  • 自适应衰减率 :根据任务类型自动调整 λ 参数
  • 时间感知的 Agent 调度 :根据 updated_at 判断哪些技能需要重新索引或刷新

结语

Gliding Horse 的时间感知系统,让 AI Agent 从"时间盲人"变成了拥有精确时间感的智能体。它不再被陈旧信息误导,能在每一次决策时清楚知道"现在几点",并自动为不同时效的信息赋予合理的权重。这是让长周期自主运行成为可能的关键基础设施。

Gliding Horse 已在 GitHub 开源:https://github.com/doiito/gliding_horse

相关推荐
日志易1 小时前
日志易5.6 AI特别版:构建行业知识库,全面升级智能运维体验
ai·智能运维·日志易
荣--2 小时前
业务智能体实战笔记:分层消除不确定性(一)|总纲:把不确定性从 LLM 侧转移到工程侧
prompt·rag·ai agent·spring ai·业务智能体
Rockbean2 小时前
10分钟Solana-性能web3-4.3 跨合约调用
rust·web3·智能合约
happyprince2 小时前
2026年07月07日全球AI前沿动态
人工智能·ai
MategoCMS3 小时前
-20°C到70°C:电子后视镜在全温域下的性能表现
ai
曦尧3 小时前
Loop Engineering:用系统设计取代手动提示,构建 AI 编码代理的自动化循环
ai·自动化
程序员爱钓鱼4 小时前
第一个 Rust 程序 Hello World
后端·rust
万亿少女的梦1684 小时前
基于Node.js和Express的旅游信息管理平台设计与开发
mysql·node.js·express·系统设计·旅游信息管理
带刺的坐椅4 小时前
代码审查 Agent Harness 实战:AI 自动 Code Review
java·ai·llm·agent·solon·soloncode·harness