Compare commits
1
Commits
main
..
05a60fc20b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05a60fc20b |
@@ -50,13 +50,19 @@ jobs:
|
|||||||
REGISTRY_USER: ${{ secrets.FORGEJO_REGISTRY_USER }}
|
REGISTRY_USER: ${{ secrets.FORGEJO_REGISTRY_USER }}
|
||||||
REGISTRY_TOKEN: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
|
REGISTRY_TOKEN: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push Docker image (SHA tag only)
|
- name: Build Docker image
|
||||||
run: |
|
run: |
|
||||||
docker build --no-cache --progress=plain \
|
docker build --no-cache --progress=plain \
|
||||||
-t "${IMAGE}:${{ steps.sha.outputs.short_sha }}" \
|
-t "${IMAGE}:${{ steps.sha.outputs.short_sha }}" \
|
||||||
|
-t "${IMAGE}:latest" \
|
||||||
-f Dockerfile .
|
-f Dockerfile .
|
||||||
|
|
||||||
|
- name: Push Docker image
|
||||||
|
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
||||||
|
run: |
|
||||||
docker push "${IMAGE}:${{ steps.sha.outputs.short_sha }}"
|
docker push "${IMAGE}:${{ steps.sha.outputs.short_sha }}"
|
||||||
echo "Pushed: ${IMAGE}:${{ steps.sha.outputs.short_sha }}"
|
docker push "${IMAGE}:latest"
|
||||||
|
echo "✓ Pushed: ${IMAGE}:${{ steps.sha.outputs.short_sha }}"
|
||||||
|
|
||||||
- name: Prune unused images
|
- name: Prune unused images
|
||||||
run: docker image prune -a --force 2>&1 | tail -3 || true
|
run: docker image prune -a --force 2>&1 | tail -3 || true
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
name: Deploy
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
|
||||||
REGISTRY: forgejo.riotpiao.com
|
|
||||||
IMAGE: forgejo.riotpiao.com/riotpiao-poimen/poimen-memory
|
|
||||||
DOCKER_HOST: tcp://localhost:2375
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
name: Tag & Push Latest
|
|
||||||
runs-on: rust
|
|
||||||
steps:
|
|
||||||
- name: Install Docker
|
|
||||||
run: apt-get update && apt-get install -y docker.io
|
|
||||||
|
|
||||||
- name: Checkout code
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Get short SHA
|
|
||||||
id: sha
|
|
||||||
run: echo "short_sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Registry login
|
|
||||||
run: |
|
|
||||||
echo "${REGISTRY_TOKEN}" | docker login "${REGISTRY}" \
|
|
||||||
--username "${REGISTRY_USER}" --password-stdin
|
|
||||||
env:
|
|
||||||
REGISTRY_USER: ${{ secrets.FORGEJO_REGISTRY_USER }}
|
|
||||||
REGISTRY_TOKEN: ${{ secrets.FORGEJO_REGISTRY_TOKEN }}
|
|
||||||
|
|
||||||
- name: Pull SHA image and tag as latest
|
|
||||||
run: |
|
|
||||||
docker pull "${IMAGE}:${{ steps.sha.outputs.short_sha }}" && \
|
|
||||||
docker tag "${IMAGE}:${{ steps.sha.outputs.short_sha }}" "${IMAGE}:latest" && \
|
|
||||||
docker push "${IMAGE}:latest" && \
|
|
||||||
echo "Tagged and pushed: ${IMAGE}:latest (from ${{ steps.sha.outputs.short_sha }})"
|
|
||||||
|
|
||||||
- name: Prune images
|
|
||||||
run: docker image prune -a --force 2>&1 | tail -3 || true
|
|
||||||
@@ -1,300 +0,0 @@
|
|||||||
/// Agent-specific entity metadata for Phase 3 Agent Self-Awareness.
|
|
||||||
///
|
|
||||||
/// These structures attach to Entity via entity_type discriminator.
|
|
||||||
/// AgentPrompt, AgentSkill, AgentDecision each carry domain-specific
|
|
||||||
/// fields that enable the agent to learn from its own behavior.
|
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use time::OffsetDateTime;
|
|
||||||
|
|
||||||
use crate::entity::{Entity, EntityType};
|
|
||||||
|
|
||||||
/// Metadata for an AgentPrompt entity.
|
|
||||||
/// Tracks prompt templates, their usage frequency, and effectiveness.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct AgentPromptMeta {
|
|
||||||
/// The prompt template text (may contain {{placeholders}}).
|
|
||||||
pub template: String,
|
|
||||||
/// Which LLM model this prompt targets (e.g. "claude-3-sonnet").
|
|
||||||
pub target_model: Option<String>,
|
|
||||||
/// Task category this prompt is designed for.
|
|
||||||
pub task_category: String,
|
|
||||||
/// Number of times this prompt has been used.
|
|
||||||
pub usage_count: u64,
|
|
||||||
/// Average quality score from outcomes (0.0-1.0).
|
|
||||||
pub avg_quality: f32,
|
|
||||||
/// Last time this prompt was used.
|
|
||||||
#[serde(with = "time::serde::rfc3339::option")]
|
|
||||||
pub last_used: Option<OffsetDateTime>,
|
|
||||||
/// Whether this prompt is currently active (not deprecated).
|
|
||||||
pub active: bool,
|
|
||||||
/// Version for tracking prompt evolution.
|
|
||||||
pub version: u32,
|
|
||||||
/// Tags for categorization.
|
|
||||||
pub tags: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Metadata for an AgentSkill entity.
|
|
||||||
/// Tracks learned capabilities and their effectiveness.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct AgentSkillMeta {
|
|
||||||
/// Description of what this skill does.
|
|
||||||
pub description: String,
|
|
||||||
/// Trigger conditions that activate this skill.
|
|
||||||
pub trigger_patterns: Vec<String>,
|
|
||||||
/// Success rate over all invocations (0.0-1.0).
|
|
||||||
pub success_rate: f32,
|
|
||||||
/// Number of times this skill was invoked.
|
|
||||||
pub invocation_count: u64,
|
|
||||||
/// Average latency in milliseconds.
|
|
||||||
pub avg_latency_ms: u64,
|
|
||||||
/// Linked prompt entity IDs that this skill uses.
|
|
||||||
pub linked_prompts: Vec<String>,
|
|
||||||
/// Whether this skill is currently enabled.
|
|
||||||
pub enabled: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Metadata for an AgentDecision entity.
|
|
||||||
/// Records a decision the agent made, including reasoning and outcome.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct AgentDecisionMeta {
|
|
||||||
/// What the agent decided to do.
|
|
||||||
pub action: String,
|
|
||||||
/// Why the agent chose this action.
|
|
||||||
pub reasoning: String,
|
|
||||||
/// Available alternatives that were considered.
|
|
||||||
pub alternatives: Vec<String>,
|
|
||||||
/// Confidence in the decision (0.0-1.0).
|
|
||||||
pub confidence: f32,
|
|
||||||
/// Outcome of the decision (set after execution).
|
|
||||||
pub outcome: Option<DecisionOutcome>,
|
|
||||||
/// Context that informed the decision (entity IDs).
|
|
||||||
pub context_entities: Vec<String>,
|
|
||||||
/// The tool/task context when decision was made.
|
|
||||||
pub tool: Option<String>,
|
|
||||||
pub task: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Outcome of an agent decision.
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
pub struct DecisionOutcome {
|
|
||||||
/// Whether the decision led to success.
|
|
||||||
pub success: bool,
|
|
||||||
/// Quality score of the outcome (0.0-1.0).
|
|
||||||
pub quality: f32,
|
|
||||||
/// Feedback or error message.
|
|
||||||
pub feedback: Option<String>,
|
|
||||||
/// When the outcome was recorded.
|
|
||||||
#[serde(with = "time::serde::rfc3339")]
|
|
||||||
pub recorded_at: OffsetDateTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Factory functions ---
|
|
||||||
|
|
||||||
/// Create a new AgentPrompt entity.
|
|
||||||
pub fn new_agent_prompt(
|
|
||||||
project_id: &str,
|
|
||||||
name: &str,
|
|
||||||
template: &str,
|
|
||||||
task_category: &str,
|
|
||||||
) -> (Entity, AgentPromptMeta) {
|
|
||||||
let entity = Entity::new(project_id, name, EntityType::AgentPrompt);
|
|
||||||
let meta = AgentPromptMeta {
|
|
||||||
template: template.to_string(),
|
|
||||||
target_model: None,
|
|
||||||
task_category: task_category.to_string(),
|
|
||||||
usage_count: 0,
|
|
||||||
avg_quality: 0.0,
|
|
||||||
last_used: None,
|
|
||||||
active: true,
|
|
||||||
version: 1,
|
|
||||||
tags: vec![],
|
|
||||||
};
|
|
||||||
(entity, meta)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new AgentSkill entity.
|
|
||||||
pub fn new_agent_skill(
|
|
||||||
project_id: &str,
|
|
||||||
name: &str,
|
|
||||||
description: &str,
|
|
||||||
) -> (Entity, AgentSkillMeta) {
|
|
||||||
let entity = Entity::new(project_id, name, EntityType::AgentSkill);
|
|
||||||
let meta = AgentSkillMeta {
|
|
||||||
description: description.to_string(),
|
|
||||||
trigger_patterns: vec![],
|
|
||||||
success_rate: 0.0,
|
|
||||||
invocation_count: 0,
|
|
||||||
avg_latency_ms: 0,
|
|
||||||
linked_prompts: vec![],
|
|
||||||
enabled: true,
|
|
||||||
};
|
|
||||||
(entity, meta)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a new AgentDecision entity.
|
|
||||||
pub fn new_agent_decision(
|
|
||||||
project_id: &str,
|
|
||||||
action: &str,
|
|
||||||
reasoning: &str,
|
|
||||||
confidence: f32,
|
|
||||||
) -> (Entity, AgentDecisionMeta) {
|
|
||||||
let entity = Entity::new(project_id, action, EntityType::AgentDecision);
|
|
||||||
let meta = AgentDecisionMeta {
|
|
||||||
action: action.to_string(),
|
|
||||||
reasoning: reasoning.to_string(),
|
|
||||||
alternatives: vec![],
|
|
||||||
confidence,
|
|
||||||
outcome: None,
|
|
||||||
context_entities: vec![],
|
|
||||||
tool: None,
|
|
||||||
task: None,
|
|
||||||
};
|
|
||||||
(entity, meta)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Record outcome for a decision.
|
|
||||||
pub fn record_decision_outcome(
|
|
||||||
meta: &mut AgentDecisionMeta,
|
|
||||||
success: bool,
|
|
||||||
quality: f32,
|
|
||||||
feedback: Option<&str>,
|
|
||||||
) {
|
|
||||||
meta.outcome = Some(DecisionOutcome {
|
|
||||||
success,
|
|
||||||
quality,
|
|
||||||
feedback: feedback.map(|s| s.to_string()),
|
|
||||||
recorded_at: OffsetDateTime::now_utc(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update prompt usage statistics.
|
|
||||||
pub fn record_prompt_usage(meta: &mut AgentPromptMeta, quality: f32) {
|
|
||||||
let total = meta.avg_quality * meta.usage_count as f32 + quality;
|
|
||||||
meta.usage_count += 1;
|
|
||||||
meta.avg_quality = total / meta.usage_count as f32;
|
|
||||||
meta.last_used = Some(OffsetDateTime::now_utc());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Update skill invocation statistics.
|
|
||||||
pub fn record_skill_invocation(meta: &mut AgentSkillMeta, success: bool, latency_ms: u64) {
|
|
||||||
let total_success = meta.success_rate * meta.invocation_count as f32
|
|
||||||
+ if success { 1.0 } else { 0.0 };
|
|
||||||
let total_latency = meta.avg_latency_ms * meta.invocation_count + latency_ms;
|
|
||||||
meta.invocation_count += 1;
|
|
||||||
meta.success_rate = total_success / meta.invocation_count as f32;
|
|
||||||
meta.avg_latency_ms = total_latency / meta.invocation_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_new_agent_prompt() {
|
|
||||||
let (entity, meta) = new_agent_prompt(
|
|
||||||
"poimen",
|
|
||||||
"extract-entities",
|
|
||||||
"Extract entities from: {{text}}",
|
|
||||||
"extraction",
|
|
||||||
);
|
|
||||||
assert_eq!(entity.entity_type, EntityType::AgentPrompt);
|
|
||||||
assert_eq!(entity.name, "extract-entities");
|
|
||||||
assert_eq!(meta.template, "Extract entities from: {{text}}");
|
|
||||||
assert_eq!(meta.task_category, "extraction");
|
|
||||||
assert_eq!(meta.usage_count, 0);
|
|
||||||
assert!(meta.active);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_new_agent_skill() {
|
|
||||||
let (entity, meta) = new_agent_skill(
|
|
||||||
"poimen",
|
|
||||||
"diagnose-pod-failure",
|
|
||||||
"Diagnose Kubernetes pod CrashLoopBackOff",
|
|
||||||
);
|
|
||||||
assert_eq!(entity.entity_type, EntityType::AgentSkill);
|
|
||||||
assert_eq!(meta.description, "Diagnose Kubernetes pod CrashLoopBackOff");
|
|
||||||
assert!(meta.enabled);
|
|
||||||
assert_eq!(meta.invocation_count, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_new_agent_decision() {
|
|
||||||
let (entity, meta) = new_agent_decision(
|
|
||||||
"poimen",
|
|
||||||
"restart-pod",
|
|
||||||
"Pod stuck in CrashLoopBackOff for 10 minutes",
|
|
||||||
0.85,
|
|
||||||
);
|
|
||||||
assert_eq!(entity.entity_type, EntityType::AgentDecision);
|
|
||||||
assert_eq!(meta.action, "restart-pod");
|
|
||||||
assert_eq!(meta.confidence, 0.85);
|
|
||||||
assert!(meta.outcome.is_none());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_record_decision_outcome() {
|
|
||||||
let (_, mut meta) = new_agent_decision("p", "act", "reason", 0.9);
|
|
||||||
assert!(meta.outcome.is_none());
|
|
||||||
|
|
||||||
record_decision_outcome(&mut meta, true, 0.95, Some("Pod recovered"));
|
|
||||||
assert!(meta.outcome.is_some());
|
|
||||||
let outcome = meta.outcome.unwrap();
|
|
||||||
assert!(outcome.success);
|
|
||||||
assert_eq!(outcome.quality, 0.95);
|
|
||||||
assert_eq!(outcome.feedback, Some("Pod recovered".to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_record_prompt_usage() {
|
|
||||||
let (_, mut meta) = new_agent_prompt("p", "test", "tmpl", "cat");
|
|
||||||
assert_eq!(meta.usage_count, 0);
|
|
||||||
assert_eq!(meta.avg_quality, 0.0);
|
|
||||||
|
|
||||||
record_prompt_usage(&mut meta, 0.8);
|
|
||||||
assert_eq!(meta.usage_count, 1);
|
|
||||||
assert_eq!(meta.avg_quality, 0.8);
|
|
||||||
|
|
||||||
record_prompt_usage(&mut meta, 1.0);
|
|
||||||
assert_eq!(meta.usage_count, 2);
|
|
||||||
assert!((meta.avg_quality - 0.9).abs() < 0.001);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_record_skill_invocation() {
|
|
||||||
let (_, mut meta) = new_agent_skill("p", "skill", "desc");
|
|
||||||
assert_eq!(meta.invocation_count, 0);
|
|
||||||
|
|
||||||
record_skill_invocation(&mut meta, true, 100);
|
|
||||||
assert_eq!(meta.invocation_count, 1);
|
|
||||||
assert_eq!(meta.success_rate, 1.0);
|
|
||||||
assert_eq!(meta.avg_latency_ms, 100);
|
|
||||||
|
|
||||||
record_skill_invocation(&mut meta, false, 200);
|
|
||||||
assert_eq!(meta.invocation_count, 2);
|
|
||||||
assert_eq!(meta.success_rate, 0.5);
|
|
||||||
assert_eq!(meta.avg_latency_ms, 150);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_entity_type_round_trip_agent_types() {
|
|
||||||
for ty in &[
|
|
||||||
EntityType::AgentPrompt,
|
|
||||||
EntityType::AgentSkill,
|
|
||||||
EntityType::AgentDecision,
|
|
||||||
] {
|
|
||||||
let s = ty.as_str();
|
|
||||||
assert_eq!(EntityType::from_str(s), *ty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_agent_prompt_serialization() {
|
|
||||||
let (_, meta) = new_agent_prompt("p", "test", "tmpl {{x}}", "cat");
|
|
||||||
let json = serde_json::to_string(&meta).unwrap();
|
|
||||||
let deserialized: AgentPromptMeta = serde_json::from_str(&json).unwrap();
|
|
||||||
assert_eq!(deserialized.template, "tmpl {{x}}");
|
|
||||||
assert_eq!(deserialized.task_category, "cat");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -17,13 +17,6 @@ pub enum EntityType {
|
|||||||
Location,
|
Location,
|
||||||
Event,
|
Event,
|
||||||
Organization,
|
Organization,
|
||||||
/// Agent prompt template tracked as a first-class entity.
|
|
||||||
/// Enables the agent to learn which prompts produce good results.
|
|
||||||
AgentPrompt,
|
|
||||||
/// Agent skill — a reusable capability the agent has learned.
|
|
||||||
AgentSkill,
|
|
||||||
/// Agent decision — a recorded choice with reasoning and outcome.
|
|
||||||
AgentDecision,
|
|
||||||
Unknown,
|
Unknown,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,9 +29,6 @@ impl EntityType {
|
|||||||
Self::Location => "location",
|
Self::Location => "location",
|
||||||
Self::Event => "event",
|
Self::Event => "event",
|
||||||
Self::Organization => "organization",
|
Self::Organization => "organization",
|
||||||
Self::AgentPrompt => "agent_prompt",
|
|
||||||
Self::AgentSkill => "agent_skill",
|
|
||||||
Self::AgentDecision => "agent_decision",
|
|
||||||
Self::Unknown => "unknown",
|
Self::Unknown => "unknown",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,9 +41,6 @@ impl EntityType {
|
|||||||
"location" => Self::Location,
|
"location" => Self::Location,
|
||||||
"event" => Self::Event,
|
"event" => Self::Event,
|
||||||
"organization" => Self::Organization,
|
"organization" => Self::Organization,
|
||||||
"agent_prompt" => Self::AgentPrompt,
|
|
||||||
"agent_skill" => Self::AgentSkill,
|
|
||||||
"agent_decision" => Self::AgentDecision,
|
|
||||||
_ => Self::Unknown,
|
_ => Self::Unknown,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,9 +175,6 @@ mod tests {
|
|||||||
EntityType::Person,
|
EntityType::Person,
|
||||||
EntityType::Tool,
|
EntityType::Tool,
|
||||||
EntityType::Concept,
|
EntityType::Concept,
|
||||||
EntityType::AgentPrompt,
|
|
||||||
EntityType::AgentSkill,
|
|
||||||
EntityType::AgentDecision,
|
|
||||||
] {
|
] {
|
||||||
let s = ty.as_str();
|
let s = ty.as_str();
|
||||||
assert_eq!(EntityType::from_str(s), *ty);
|
assert_eq!(EntityType::from_str(s), *ty);
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ pub mod scoring;
|
|||||||
pub mod entity;
|
pub mod entity;
|
||||||
pub mod edge;
|
pub mod edge;
|
||||||
pub mod community;
|
pub mod community;
|
||||||
pub mod agent_entity;
|
|
||||||
|
|
||||||
pub use gate_parser::{GateResponse, ParseError, parse_gate_response};
|
pub use gate_parser::{GateResponse, ParseError, parse_gate_response};
|
||||||
|
|
||||||
@@ -31,4 +30,3 @@ pub use scoring::{DocumentScorer, ScoringPipeline, GlobalTfIdfScorer, ProjectTfI
|
|||||||
pub use entity::{Entity, EntityType};
|
pub use entity::{Entity, EntityType};
|
||||||
pub use edge::{Edge, ContradictionStatus};
|
pub use edge::{Edge, ContradictionStatus};
|
||||||
pub use community::Community;
|
pub use community::Community;
|
||||||
pub use agent_entity::{AgentPromptMeta, AgentSkillMeta, AgentDecisionMeta, DecisionOutcome};
|
|
||||||
|
|||||||
Reference in New Issue
Block a user