diff --git a/.github/workflows/external-memory-pattern-radar.yml b/.github/workflows/external-memory-pattern-radar.yml new file mode 100644 index 00000000..92fa2af2 --- /dev/null +++ b/.github/workflows/external-memory-pattern-radar.yml @@ -0,0 +1,47 @@ +name: External Memory Pattern Radar + +permissions: + contents: read + +on: + workflow_dispatch: + schedule: + # Weekly on Wednesday at 04:20 UTC. + - cron: "20 4 * * 3" + +concurrency: + group: external-memory-pattern-radar + cancel-in-progress: true + +jobs: + radar: + name: Run read-only radar artifact refresh + runs-on: ubuntu-latest + steps: + - name: Fetch latest code + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 + + - name: Set up Rust toolchain + uses: actions-rust-lang/setup-rust-toolchain@46268bd060767258de96ed93c1251119784f2ab6 + with: + cache: true + rustflags: "" + + - name: Install cargo-make + uses: taiki-e/install-action@0631aa6515c7d545823c67cfae7ef4fc7f490154 + with: + tool: cargo-make + + - name: Run radar artifact refresh + run: cargo make external-memory-radar-artifact + + - name: Upload radar artifacts + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a + with: + name: external-memory-pattern-radar-${{ github.run_id }} + if-no-files-found: error + retention-days: 30 + path: | + tmp/external-memory-pattern-radar/cursor.json + tmp/external-memory-pattern-radar/latest.md diff --git a/Cargo.lock b/Cargo.lock index b95af3e4..512b2d80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1039,6 +1039,7 @@ dependencies = [ "elf-storage", "elf-testkit", "elf-worker", + "reqwest 0.13.4", "serde", "serde_json", "sqlx", diff --git a/Makefile.toml b/Makefile.toml index ebe6d208..432cf54b 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -872,6 +872,135 @@ args = [ ] +# External memory pattern radar +# | task | type | cwd | +# | ---------------------------------- | --------- | --- | +# | external-memory-radar | command | | +# | external-memory-radar-artifact | composite | | +# | external-memory-radar-artifact-json | command | | +# | external-memory-radar-artifact-validate | command | | +# | external-memory-radar-dry-run | composite | | +# | external-memory-radar-dry-run-json | command | | +# | external-memory-radar-dry-run-validate | command | | +# | external-memory-radar-validate | command | | + +[tasks.external-memory-radar] +workspace = false +command = "cargo" +args = [ + "run", + "-p", + "elf-eval", + "--bin", + "external_memory_pattern_radar", + "--", + "run", + "--cursor", + "docs/research/external_memory_pattern_radar/cursor.json", + "--summary", + "docs/research/external_memory_pattern_radar/latest.md", +] + +[tasks.external-memory-radar-artifact] +workspace = false +dependencies = [ + "external-memory-radar-artifact-json", + "external-memory-radar-artifact-validate", +] + +[tasks.external-memory-radar-artifact-json] +workspace = false +command = "cargo" +args = [ + "run", + "-p", + "elf-eval", + "--bin", + "external_memory_pattern_radar", + "--", + "run", + "--cursor", + "docs/research/external_memory_pattern_radar/cursor.json", + "--out-cursor", + "tmp/external-memory-pattern-radar/cursor.json", + "--summary", + "tmp/external-memory-pattern-radar/latest.md", +] + +[tasks.external-memory-radar-artifact-validate] +workspace = false +command = "cargo" +args = [ + "run", + "-p", + "elf-eval", + "--bin", + "external_memory_pattern_radar", + "--", + "validate", + "--cursor", + "tmp/external-memory-pattern-radar/cursor.json", +] + +[tasks.external-memory-radar-dry-run] +workspace = false +dependencies = [ + "external-memory-radar-dry-run-json", + "external-memory-radar-dry-run-validate", +] + +[tasks.external-memory-radar-dry-run-json] +workspace = false +command = "cargo" +args = [ + "run", + "-p", + "elf-eval", + "--bin", + "external_memory_pattern_radar", + "--", + "run", + "--mode", + "offline", + "--cursor", + "docs/research/external_memory_pattern_radar/cursor.json", + "--out-cursor", + "tmp/external-memory-pattern-radar/cursor.json", + "--summary", + "tmp/external-memory-pattern-radar/latest.md", +] + +[tasks.external-memory-radar-dry-run-validate] +workspace = false +command = "cargo" +args = [ + "run", + "-p", + "elf-eval", + "--bin", + "external_memory_pattern_radar", + "--", + "validate", + "--cursor", + "tmp/external-memory-pattern-radar/cursor.json", +] + +[tasks.external-memory-radar-validate] +workspace = false +command = "cargo" +args = [ + "run", + "-p", + "elf-eval", + "--bin", + "external_memory_pattern_radar", + "--", + "validate", + "--cursor", + "docs/research/external_memory_pattern_radar/cursor.json", +] + + # Meta # | task | type | cwd | # | ------ | --------- | --- | diff --git a/apps/elf-eval/Cargo.toml b/apps/elf-eval/Cargo.toml index 149e81f5..6f676ad9 100644 --- a/apps/elf-eval/Cargo.toml +++ b/apps/elf-eval/Cargo.toml @@ -9,6 +9,7 @@ version = "0.2.0" blake3 = { workspace = true } clap = { workspace = true } color-eyre = { workspace = true } +reqwest = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } sqlx = { workspace = true } diff --git a/apps/elf-eval/src/bin/external_memory_pattern_radar.rs b/apps/elf-eval/src/bin/external_memory_pattern_radar.rs new file mode 100644 index 00000000..9a843a7b --- /dev/null +++ b/apps/elf-eval/src/bin/external_memory_pattern_radar.rs @@ -0,0 +1,821 @@ +#![allow(unused_crate_dependencies)] + +//! Weekly external memory pattern radar runner. + +use std::{ + collections::BTreeSet, + env, fs, + path::{Path, PathBuf}, +}; + +use clap::{Parser, Subcommand, ValueEnum}; +use color_eyre::{Result, eyre}; +use reqwest::{ + Client, StatusCode, + header::{ACCEPT, AUTHORIZATION, HeaderMap, HeaderValue, USER_AGENT}, +}; +use serde::{Deserialize, Serialize}; +use time::{OffsetDateTime, format_description::well_known::Rfc3339}; + +const CURSOR_SCHEMA: &str = "elf.external_memory_pattern_radar_cursor/v1"; +const RUN_SCHEMA: &str = "elf.external_memory_pattern_radar_run/v1"; +const DEFAULT_CURSOR: &str = "docs/research/external_memory_pattern_radar/cursor.json"; +const DEFAULT_SUMMARY: &str = "docs/research/external_memory_pattern_radar/latest.md"; + +#[derive(Debug, Parser)] +#[command( + version = elf_cli::VERSION, + rename_all = "kebab", + styles = elf_cli::styles(), +)] +struct Args { + #[command(subcommand)] + command: Command, +} + +#[derive(Debug, Parser)] +struct RunArgs { + /// Existing radar cursor file. + #[arg(long, value_name = "FILE", default_value = DEFAULT_CURSOR)] + cursor: PathBuf, + /// Output cursor path. Defaults to updating --cursor. + #[arg(long, value_name = "FILE")] + out_cursor: Option, + /// Output Markdown summary path. + #[arg(long, value_name = "FILE", default_value = DEFAULT_SUMMARY)] + summary: PathBuf, + /// Observation mode. Use offline for deterministic dry runs. + #[arg(long, value_enum, default_value_t = RadarMode::Live)] + mode: RadarMode, + /// Stable run id. Defaults to external-memory-pattern-radar-YYYY-MM-DD. + #[arg(long)] + run_id: Option, + /// Environment variable containing a GitHub token for live mode. + #[arg(long, default_value = "GITHUB_TOKEN")] + github_token_env: String, +} + +#[derive(Debug, Parser)] +struct ValidateArgs { + /// Cursor file to validate. + #[arg(long, value_name = "FILE", default_value = DEFAULT_CURSOR)] + cursor: PathBuf, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +struct RadarCursor { + schema: String, + cadence: String, + generated_at: String, + source_docs: Vec, + projects: Vec, + last_run: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +struct RadarProject { + id: String, + name: String, + repo: String, + homepage: String, + watch_focus: Vec, + primary_references: Vec, + coverage_evidence: Vec, + last_seen: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +struct EvidenceRef { + label: String, + path: String, + summary: String, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +struct ProjectObservation { + observed_at: String, + source_url: String, + default_branch: Option, + pushed_at: Option, + updated_at: Option, + latest_release: Option, + stars: Option, + open_issues: Option, + description: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +struct ReleaseObservation { + tag_name: String, + url: String, + published_at: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +struct RadarRun { + schema: String, + run_id: String, + generated_at: String, + mode: RadarMode, + summary: RunSummary, + decisions: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +struct RunSummary { + project_count: usize, + covered_count: usize, + rejected_count: usize, + gap_count: usize, + create_issue_count: usize, + defer_count: usize, + no_issue_count: usize, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +struct RadarDecision { + project_id: String, + upstream_change: String, + reusable_pattern: String, + elf_verdict: ElfVerdict, + product_value: String, + duplicate_coverage_evidence: Vec, + safety_boundary: String, + issue_decision: IssueDecision, + acceptance_evidence: Vec, + source_links: Vec, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +struct IssueDecision { + action: IssueAction, + rationale: String, + duplicate_search: DuplicateSearchEvidence, + proposed_issue: Option, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +struct DuplicateSearchEvidence { + queried: bool, + query: String, + result: DuplicateSearchResult, + evidence: Vec, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +struct ProposedIssue { + title: String, + source_links: Vec, + repo_evidence: Vec, + non_goals: Vec, + validation_criteria: Vec, +} + +#[derive(Debug, Deserialize)] +struct GithubRepoResponse { + html_url: String, + default_branch: Option, + pushed_at: Option, + updated_at: Option, + stargazers_count: Option, + open_issues_count: Option, + description: Option, +} + +#[derive(Debug, Deserialize)] +struct GithubReleaseResponse { + tag_name: String, + html_url: String, + published_at: Option, +} + +#[derive(Debug, Subcommand)] +#[command(rename_all = "kebab")] +enum Command { + /// Run the external memory radar and write cursor plus Markdown summary. + Run(RunArgs), + /// Validate a radar cursor and its latest decision records. + Validate(ValidateArgs), +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize, ValueEnum)] +#[serde(rename_all = "snake_case")] +enum RadarMode { + Live, + Offline, +} +impl RadarMode { + fn as_str(self) -> &'static str { + match self { + Self::Live => "live", + Self::Offline => "offline", + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +enum ElfVerdict { + Covered, + Reject, + Gap, +} +impl ElfVerdict { + fn as_str(self) -> &'static str { + match self { + Self::Covered => "covered", + Self::Reject => "reject", + Self::Gap => "gap", + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +enum IssueAction { + NoIssue, + Defer, + CreateIssue, +} +impl IssueAction { + fn as_str(self) -> &'static str { + match self { + Self::NoIssue => "no_issue", + Self::Defer => "defer", + Self::CreateIssue => "create_issue", + } + } +} + +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[serde(rename_all = "snake_case")] +enum DuplicateSearchResult { + NotRequiredNoIssue, + NoDuplicateFound, + DuplicateFound, +} + +fn validate_command(path: &Path) -> Result<()> { + let cursor = read_cursor(path)?; + + validate_cursor(&cursor) +} + +fn read_cursor(path: &Path) -> Result { + let raw = fs::read_to_string(path) + .map_err(|err| eyre::eyre!("failed to read cursor {}: {err}", path.display()))?; + let cursor = serde_json::from_str(&raw) + .map_err(|err| eyre::eyre!("failed to parse cursor {}: {err}", path.display()))?; + + Ok(cursor) +} + +fn write_json(path: &Path, value: &T) -> Result<()> +where + T: Serialize, +{ + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + + let raw = serde_json::to_string_pretty(value)?; + + fs::write(path, format!("{raw}\n"))?; + + Ok(()) +} + +fn write_text(path: &Path, content: &str) -> Result<()> { + if let Some(parent) = path.parent() { + fs::create_dir_all(parent)?; + } + + fs::write(path, content)?; + + Ok(()) +} + +fn github_client(token_env: &str) -> Result> { + let mut headers = HeaderMap::new(); + + headers.insert(USER_AGENT, HeaderValue::from_static("elf-external-memory-pattern-radar")); + headers.insert(ACCEPT, HeaderValue::from_static("application/vnd.github+json")); + + if let Ok(token) = env::var(token_env) + && !token.trim().is_empty() + { + let value = format!("Bearer {}", token.trim()).parse()?; + + headers.insert(AUTHORIZATION, value); + } + + Ok(Some(Client::builder().default_headers(headers).build()?)) +} + +fn fallback_observation(project: &RadarProject, generated_at: &str) -> ProjectObservation { + ProjectObservation { + observed_at: generated_at.to_string(), + source_url: project.homepage.clone(), + default_branch: None, + pushed_at: None, + updated_at: None, + latest_release: None, + stars: None, + open_issues: None, + description: None, + } +} + +fn decide_project( + project: &RadarProject, + prior: Option<&ProjectObservation>, + observed: &ProjectObservation, + mode: RadarMode, +) -> RadarDecision { + let source_links = source_links(project, observed); + let evidence = project.coverage_evidence.clone(); + let changed = prior.map(|previous| observation_changed(previous, observed)).unwrap_or(false); + + if changed { + return RadarDecision { + project_id: project.id.clone(), + upstream_change: metadata_delta(prior, observed), + reusable_pattern: "No reusable pattern is claimed from metadata alone; source review is required before a pattern can become a gap." + .to_string(), + elf_verdict: ElfVerdict::Reject, + product_value: "Metadata movement is useful as a review trigger, but it has no product value until source evidence identifies a reusable pattern." + .to_string(), + duplicate_coverage_evidence: evidence, + safety_boundary: "Reject issue creation from activity, star counts, release tags, or push timestamps alone." + .to_string(), + issue_decision: IssueDecision { + action: IssueAction::NoIssue, + rationale: "No issue was created because this run only proved a metadata delta; the Codex review step must gather source links, repo evidence, and Linear duplicate search first." + .to_string(), + duplicate_search: DuplicateSearchEvidence { + queried: false, + query: String::new(), + result: DuplicateSearchResult::NotRequiredNoIssue, + evidence: vec![ + "No Linear search is required when the issue decision is no_issue.".to_string(), + ], + }, + proposed_issue: None, + }, + acceptance_evidence: vec![ + "Metadata delta recorded in the structured cursor.".to_string(), + "No parity or adoption claim was made from activity alone.".to_string(), + ], + source_links, + }; + } + + let upstream_change = if prior.is_none() { + metadata_delta(None, observed) + } else { + match mode { + RadarMode::Live => + "No GitHub metadata delta was observed since the prior cursor.".to_string(), + RadarMode::Offline => + "No upstream fetch was performed; the dry run replayed the checked-in cursor." + .to_string(), + } + }; + + RadarDecision { + project_id: project.id.clone(), + upstream_change, + reusable_pattern: "No new candidate pattern was identified in this run.".to_string(), + elf_verdict: ElfVerdict::Covered, + product_value: "Current ELF coverage remains represented by the comparison and inventory evidence." + .to_string(), + duplicate_coverage_evidence: evidence, + safety_boundary: "No external runtime is adopted by default; existing ELF evidence remains authoritative." + .to_string(), + issue_decision: IssueDecision { + action: IssueAction::NoIssue, + rationale: "No issue was created because the run found no source-backed gap.".to_string(), + duplicate_search: DuplicateSearchEvidence { + queried: false, + query: String::new(), + result: DuplicateSearchResult::NotRequiredNoIssue, + evidence: vec![ + "No Linear search is required when the issue decision is no_issue.".to_string(), + ], + }, + proposed_issue: None, + }, + acceptance_evidence: vec![ + "No-issue decision recorded in the cursor.".to_string(), + "Coverage evidence points at checked-in ELF research docs.".to_string(), + ], + source_links, + } +} + +fn source_links(project: &RadarProject, observed: &ProjectObservation) -> Vec { + let mut links = BTreeSet::new(); + + links.insert(project.homepage.clone()); + links.insert(observed.source_url.clone()); + + if let Some(release) = &observed.latest_release { + links.insert(release.url.clone()); + } + + links.into_iter().collect() +} + +fn observation_changed(previous: &ProjectObservation, observed: &ProjectObservation) -> bool { + previous.pushed_at != observed.pushed_at + || previous.updated_at != observed.updated_at + || previous.latest_release.as_ref().map(|release| &release.tag_name) + != observed.latest_release.as_ref().map(|release| &release.tag_name) +} + +fn metadata_delta(prior: Option<&ProjectObservation>, observed: &ProjectObservation) -> String { + let Some(previous) = prior else { + return "First cursor observation recorded; no prior state exists for comparison." + .to_string(); + }; + let previous_release = + previous.latest_release.as_ref().map(|release| release.tag_name.as_str()).unwrap_or("none"); + let observed_release = + observed.latest_release.as_ref().map(|release| release.tag_name.as_str()).unwrap_or("none"); + + format!( + "Repository metadata changed: pushed_at {} -> {}, latest_release {} -> {}.", + previous.pushed_at.as_deref().unwrap_or("unknown"), + observed.pushed_at.as_deref().unwrap_or("unknown"), + previous_release, + observed_release + ) +} + +fn summarize_decisions(decisions: &[RadarDecision]) -> RunSummary { + let mut summary = RunSummary { project_count: decisions.len(), ..RunSummary::default() }; + + for decision in decisions { + match decision.elf_verdict { + ElfVerdict::Covered => summary.covered_count += 1, + ElfVerdict::Reject => summary.rejected_count += 1, + ElfVerdict::Gap => summary.gap_count += 1, + } + match decision.issue_decision.action { + IssueAction::NoIssue => summary.no_issue_count += 1, + IssueAction::Defer => summary.defer_count += 1, + IssueAction::CreateIssue => summary.create_issue_count += 1, + } + } + + summary +} + +fn validate_cursor(cursor: &RadarCursor) -> Result<()> { + let mut errors = Vec::new(); + + if cursor.schema != CURSOR_SCHEMA { + errors.push(format!("cursor schema must be {CURSOR_SCHEMA}")); + } + if cursor.projects.is_empty() { + errors.push("cursor must include at least one project".to_string()); + } + + let project_ids = + cursor.projects.iter().map(|project| project.id.as_str()).collect::>(); + + if project_ids.len() != cursor.projects.len() { + errors.push("project ids must be unique".to_string()); + } + + for project in &cursor.projects { + validate_project(project, &mut errors); + } + + if let Some(run) = &cursor.last_run { + validate_run(run, &project_ids, &mut errors); + } + + if errors.is_empty() { + Ok(()) + } else { + Err(eyre::eyre!("radar cursor validation failed:\n{}", errors.join("\n"))) + } +} + +fn validate_project(project: &RadarProject, errors: &mut Vec) { + if project.id.trim().is_empty() { + errors.push("project id must not be empty".to_string()); + } + if !project.repo.contains('/') { + errors.push(format!("project {} repo must be owner/name", project.id)); + } + if project.coverage_evidence.is_empty() { + errors.push(format!("project {} must include duplicate/coverage evidence", project.id)); + } +} + +fn validate_run(run: &RadarRun, project_ids: &BTreeSet<&str>, errors: &mut Vec) { + if run.schema != RUN_SCHEMA { + errors.push(format!("run schema must be {RUN_SCHEMA}")); + } + if run.decisions.len() != project_ids.len() { + errors.push("latest run must include one decision per project".to_string()); + } + + for decision in &run.decisions { + validate_decision(decision, project_ids, errors); + } +} + +fn validate_decision( + decision: &RadarDecision, + project_ids: &BTreeSet<&str>, + errors: &mut Vec, +) { + if !project_ids.contains(decision.project_id.as_str()) { + errors.push(format!("decision references unknown project {}", decision.project_id)); + } + + for (field, value) in [ + ("upstream_change", &decision.upstream_change), + ("reusable_pattern", &decision.reusable_pattern), + ("product_value", &decision.product_value), + ("safety_boundary", &decision.safety_boundary), + ] { + if value.trim().is_empty() { + errors.push(format!("decision {} has empty {field}", decision.project_id)); + } + } + + if decision.duplicate_coverage_evidence.is_empty() { + errors.push(format!( + "decision {} must include duplicate/coverage evidence", + decision.project_id + )); + } + if decision.acceptance_evidence.is_empty() { + errors.push(format!("decision {} must include acceptance evidence", decision.project_id)); + } + if decision.source_links.is_empty() { + errors.push(format!("decision {} must include source links", decision.project_id)); + } + + validate_issue_decision(decision, errors); +} + +fn validate_issue_decision(decision: &RadarDecision, errors: &mut Vec) { + let issue_decision = &decision.issue_decision; + + if issue_decision.rationale.trim().is_empty() { + errors.push(format!("decision {} issue rationale must not be empty", decision.project_id)); + } + + match issue_decision.action { + IssueAction::CreateIssue => validate_create_issue(decision, errors), + IssueAction::NoIssue => + if issue_decision.proposed_issue.is_some() { + errors.push(format!( + "decision {} must not include proposed_issue for no_issue", + decision.project_id + )); + }, + IssueAction::Defer => {}, + } +} + +fn validate_create_issue(decision: &RadarDecision, errors: &mut Vec) { + let issue_decision = &decision.issue_decision; + + if decision.elf_verdict != ElfVerdict::Gap { + errors.push(format!( + "decision {} can create issues only for gap verdicts", + decision.project_id + )); + } + if !issue_decision.duplicate_search.queried { + errors.push(format!( + "decision {} must search Linear before issue creation", + decision.project_id + )); + } + + let Some(proposed_issue) = &issue_decision.proposed_issue else { + errors.push(format!( + "decision {} create_issue must include proposed_issue", + decision.project_id + )); + + return; + }; + + if proposed_issue.source_links.is_empty() + || proposed_issue.repo_evidence.is_empty() + || proposed_issue.non_goals.is_empty() + || proposed_issue.validation_criteria.is_empty() + { + errors.push(format!( + "decision {} proposed issue must include source links, repo evidence, non-goals, and validation criteria", + decision.project_id + )); + } +} + +fn render_summary(cursor: &RadarCursor) -> Result { + let run = cursor.last_run.as_ref().ok_or_else(|| eyre::eyre!("cursor has no last_run"))?; + let mut out = String::new(); + + out.push_str("# External Memory Pattern Radar Summary\n\n"); + out.push_str("Goal: Preserve the latest weekly ELF external memory pattern radar outcome.\n"); + out.push_str("Read this when: Feeding the next full comparison report or deciding whether a watched upstream memory project created an ELF follow-up.\n"); + out.push_str("Inputs: `docs/research/external_memory_pattern_radar/cursor.json`, GitHub repository metadata, checked-in ELF comparison evidence, and any Codex source-review notes.\n"); + out.push_str("Depends on: `docs/spec/external_memory_pattern_radar_v1.md` and `docs/guide/research/external_memory_pattern_radar.md`.\n"); + out.push_str("Outputs: Latest no-issue, rejection, or issue-ready radar decisions.\n\n"); + out.push_str(&format!("- Run id: `{}`\n", run.run_id)); + out.push_str(&format!("- Generated at: `{}`\n", run.generated_at)); + out.push_str(&format!("- Mode: `{}`\n", run.mode.as_str())); + out.push_str(&format!( + "- Projects: `{}`; covered: `{}`; rejected: `{}`; gaps: `{}`; create_issue: `{}`\n\n", + run.summary.project_count, + run.summary.covered_count, + run.summary.rejected_count, + run.summary.gap_count, + run.summary.create_issue_count + )); + out.push_str("## Decisions\n\n"); + out.push_str( + "| Project | Upstream change | ELF verdict | Issue decision | Acceptance evidence |\n", + ); + out.push_str("| --- | --- | --- | --- | --- |\n"); + + for decision in &run.decisions { + out.push_str(&format!( + "| `{}` | {} | `{}` | `{}` | {} |\n", + decision.project_id, + escape_markdown_table(&decision.upstream_change), + decision.elf_verdict.as_str(), + decision.issue_decision.action.as_str(), + escape_markdown_table(&decision.acceptance_evidence.join("; ")) + )); + } + + out.push_str("\n## Safety Boundary\n\n"); + out.push_str("- The radar records upstream movement as a trigger for source review, not as proof of parity or a reason to adopt an external runtime.\n"); + out.push_str("- `create_issue` decisions are valid only when the cursor includes source links, repo evidence, non-goals, validation criteria, and Linear duplicate-search evidence.\n"); + out.push_str("- No-issue runs remain useful because each project records why ELF is already covered or why metadata-only movement was rejected.\n"); + + Ok(out) +} + +fn escape_markdown_table(value: &str) -> String { + value.replace('|', "\\|").replace('\n', " ") +} + +fn format_rfc3339(value: OffsetDateTime) -> Result { + Ok(value.format(&Rfc3339)?) +} + +#[tokio::main] +async fn main() -> Result<()> { + color_eyre::install()?; + + match Args::parse().command { + Command::Run(args) => run_radar(args).await, + Command::Validate(args) => validate_command(&args.cursor), + } +} + +async fn run_radar(args: RunArgs) -> Result<()> { + let now = OffsetDateTime::now_utc(); + let generated_at = format_rfc3339(now)?; + let run_id = + args.run_id.unwrap_or_else(|| format!("external-memory-pattern-radar-{}", now.date())); + let client = github_client(&args.github_token_env)?; + let mut cursor = read_cursor(&args.cursor)?; + let mut decisions = Vec::with_capacity(cursor.projects.len()); + + for project in &mut cursor.projects { + let prior = project.last_seen.clone(); + let observed = observe_project(project, args.mode, client.as_ref(), &generated_at).await?; + + decisions.push(decide_project(project, prior.as_ref(), &observed, args.mode)); + + project.last_seen = Some(observed); + } + + let summary = summarize_decisions(&decisions); + + cursor.generated_at = generated_at.clone(); + cursor.last_run = Some(RadarRun { + schema: RUN_SCHEMA.to_string(), + run_id, + generated_at, + mode: args.mode, + summary, + decisions, + }); + + validate_cursor(&cursor)?; + + let out_cursor = args.out_cursor.unwrap_or(args.cursor); + + write_json(&out_cursor, &cursor)?; + write_text(&args.summary, &render_summary(&cursor)?)?; + + Ok(()) +} + +async fn observe_project( + project: &RadarProject, + mode: RadarMode, + client: Option<&Client>, + generated_at: &str, +) -> Result { + match mode { + RadarMode::Offline => Ok(project + .last_seen + .clone() + .unwrap_or_else(|| fallback_observation(project, generated_at))), + RadarMode::Live => + fetch_project( + project, + client.ok_or_else(|| eyre::eyre!("missing GitHub client"))?, + generated_at, + ) + .await, + } +} + +async fn fetch_project( + project: &RadarProject, + client: &Client, + generated_at: &str, +) -> Result { + let repo = fetch_repo(project, client).await?; + let latest_release = fetch_latest_release(project, client).await?; + + Ok(ProjectObservation { + observed_at: generated_at.to_string(), + source_url: repo.html_url, + default_branch: repo.default_branch, + pushed_at: repo.pushed_at, + updated_at: repo.updated_at, + latest_release, + stars: repo.stargazers_count, + open_issues: repo.open_issues_count, + description: repo.description, + }) +} + +async fn fetch_repo(project: &RadarProject, client: &Client) -> Result { + let url = format!("https://api.github.com/repos/{}", project.repo); + let response = client.get(url).send().await?; + + if !response.status().is_success() { + return Err(eyre::eyre!( + "GitHub repo metadata fetch failed for {} with status {}", + project.repo, + response.status() + )); + } + + Ok(response.json().await?) +} + +async fn fetch_latest_release( + project: &RadarProject, + client: &Client, +) -> Result> { + let url = format!("https://api.github.com/repos/{}/releases/latest", project.repo); + let response = client.get(url).send().await?; + + if response.status() == StatusCode::NOT_FOUND { + return Ok(None); + } + if !response.status().is_success() { + return Err(eyre::eyre!( + "GitHub release metadata fetch failed for {} with status {}", + project.repo, + response.status() + )); + } + + let release: GithubReleaseResponse = response.json().await?; + + Ok(Some(ReleaseObservation { + tag_name: release.tag_name, + url: release.html_url, + published_at: release.published_at, + })) +} diff --git a/docs/guide/research/external_memory_pattern_radar.md b/docs/guide/research/external_memory_pattern_radar.md new file mode 100644 index 00000000..06638e2a --- /dev/null +++ b/docs/guide/research/external_memory_pattern_radar.md @@ -0,0 +1,89 @@ +# External Memory Pattern Radar + +Goal: Run ELF's weekly external memory pattern radar and preserve no-issue, rejection, +or issue-ready outcomes for future comparison reports. +Read this when: You are refreshing upstream memory/RAG/agent-continuity watch state or +deciding whether a watched upstream pattern deserves an ELF follow-up issue. +Inputs: `docs/research/external_memory_pattern_radar/cursor.json`, GitHub repository +metadata, current ELF research docs, and Linear duplicate-search readback when creating +issues. +Depends on: `docs/spec/external_memory_pattern_radar_v1.md`, +`docs/guide/research/comparison_external_projects.md`, and +`docs/guide/research/research_projects_inventory.md`. +Outputs: Updated cursor JSON plus `docs/research/external_memory_pattern_radar/latest.md`. + +## Scope + +The radar watches agentmemory, mem0, qmd, claude-mem, OpenViking, Graphiti, Letta, +LightRAG, GraphRAG, RAGFlow, and adjacent projects already represented in ELF's +external comparison research. + +The radar does not adopt external runtimes by default and does not create follow-up +issues from stars, activity, release tags, or push timestamps alone. + +## Commands + +Run a live cursor refresh: + +```sh +cargo make external-memory-radar +``` + +Run the deterministic no-network dry run used by local PR checks and fallback +verification: + +```sh +cargo make external-memory-radar-dry-run +``` + +Run a live read-only artifact refresh under `tmp/` without changing checked-in files: + +```sh +cargo make external-memory-radar-artifact +``` + +Validate the checked-in cursor: + +```sh +cargo make external-memory-radar-validate +``` + +## Issue Decision Rules + +For every candidate pattern, the cursor decision must record: + +- upstream change +- reusable pattern +- ELF verdict: `covered`, `reject`, or `gap` +- product value +- duplicate/coverage evidence +- safety boundary +- issue decision +- acceptance evidence + +`create_issue` is allowed only when the decision also records upstream source links, +repo evidence, non-goals, validation criteria, and Linear duplicate-search evidence. +When the run is no-issue, the cursor still records why the pattern is already covered +or why the observed change is rejected. + +## Weekly Schedule + +`.github/workflows/external-memory-pattern-radar.yml` runs weekly and on manual +dispatch. The scheduled workflow refreshes live GitHub metadata and writes artifacts under +`tmp/external-memory-pattern-radar/` and uploads them for review. + +The workflow is intentionally read-only with respect to Linear and repository contents. +Codex or Decodex automation may consume the artifact, perform source review, search +Linear, and then submit a small PR that updates the cursor and prose summary. + +## Next Comparison Report Input + +The next full comparison report should consume: + +- changed project metadata from `projects[].last_seen` +- no-issue and rejection rationales from `last_run.decisions[]` +- issue-ready `gap` records only when `issue_decision.action = "create_issue"` +- source links, repo evidence, non-goals, and validation criteria from proposed issues + +Do not quote a watched project as an ELF gap or parity win unless the cursor decision +contains source-backed evidence under the radar spec. diff --git a/docs/guide/research/index.md b/docs/guide/research/index.md index d3fb7912..cf11bc56 100644 --- a/docs/guide/research/index.md +++ b/docs/guide/research/index.md @@ -12,6 +12,8 @@ Outputs: The smallest comparison or inventory document needed for implementation - `comparison_external_projects.md`: detailed capability comparison, project trade-offs, source map, and research-backed ELF directions. - `external_memory_improvement_plan.md`: prioritized June 2026 improvement backlog, issue queue, parallelization plan, and production-adoption gate from benchmark and external-project evidence. - `agentmemory_adapter.md`: fixture-backed agentmemory import and baseline adapter boundary for `elf-eval`. +- `external_memory_pattern_radar.md`: weekly radar runbook for upstream memory-system + deltas, no-issue decisions, and issue-ready pattern evidence. ## Machine-Readable Runs diff --git a/docs/research/external_memory_pattern_radar/cursor.json b/docs/research/external_memory_pattern_radar/cursor.json new file mode 100644 index 00000000..2ce50573 --- /dev/null +++ b/docs/research/external_memory_pattern_radar/cursor.json @@ -0,0 +1,1183 @@ +{ + "schema": "elf.external_memory_pattern_radar_cursor/v1", + "cadence": "weekly", + "generated_at": "2026-06-10T08:32:00.790878Z", + "source_docs": [ + "docs/guide/research/external_memory_improvement_plan.md", + "docs/guide/research/comparison_external_projects.md", + "docs/guide/research/research_projects_inventory.md", + "docs/spec/external_memory_pattern_radar_v1.md" + ], + "projects": [ + { + "id": "agentmemory", + "name": "agentmemory", + "repo": "rohitg00/agentmemory", + "homepage": "https://github.com/rohitg00/agentmemory", + "watch_focus": [ + "rw.operator-continuity", + "rw.resume-evidence", + "rw.lifecycle-staleness" + ], + "primary_references": [ + "docs/guide/research/comparison_external_projects.md", + "docs/research/2026-06-08-agent-memory-selection.json", + "docs/research/2026-06-09-xy-841-external-memory-benchmark-dimensions.json" + ], + "coverage_evidence": [ + { + "label": "adapter evidence boundary", + "path": "docs/guide/research/comparison_external_projects.md", + "summary": "agentmemory is tracked for operator continuity and resume evidence, but current benchmark evidence does not prove durable lifecycle quality." + } + ], + "last_seen": { + "observed_at": "2026-06-10T08:32:00.790878Z", + "source_url": "https://github.com/rohitg00/agentmemory", + "default_branch": "main", + "pushed_at": "2026-06-09T15:14:55Z", + "updated_at": "2026-06-10T08:30:03Z", + "latest_release": { + "tag_name": "v0.9.27", + "url": "https://github.com/rohitg00/agentmemory/releases/tag/v0.9.27", + "published_at": "2026-06-07T08:58:35Z" + }, + "stars": 22180, + "open_issues": 264, + "description": "#1 Persistent memory for AI coding agents based on real-world benchmarks" + } + }, + { + "id": "mem0", + "name": "mem0 / OpenMemory", + "repo": "mem0ai/mem0", + "homepage": "https://github.com/mem0ai/mem0", + "watch_focus": [ + "rw.lifecycle-staleness", + "rw.graph-temporal", + "rw.operator-continuity" + ], + "primary_references": [ + "docs/guide/research/comparison_external_projects.md", + "docs/research/2026-06-09-xy-841-external-memory-benchmark-dimensions.json" + ], + "coverage_evidence": [ + { + "label": "lifecycle and graph reference", + "path": "docs/guide/research/comparison_external_projects.md", + "summary": "mem0 remains the ecosystem and entity-scoped lifecycle reference while ELF keeps deterministic evidence-bound writes." + } + ], + "last_seen": { + "observed_at": "2026-06-10T08:32:00.790878Z", + "source_url": "https://github.com/mem0ai/mem0", + "default_branch": "main", + "pushed_at": "2026-06-10T07:16:28Z", + "updated_at": "2026-06-10T08:18:56Z", + "latest_release": { + "tag_name": "cli-node-v0.2.8", + "url": "https://github.com/mem0ai/mem0/releases/tag/cli-node-v0.2.8", + "published_at": "2026-06-01T20:18:36Z" + }, + "stars": 58237, + "open_issues": 413, + "description": "Universal memory layer for AI Agents" + } + }, + { + "id": "qmd", + "name": "qmd", + "repo": "tobi/qmd", + "homepage": "https://github.com/tobi/qmd", + "watch_focus": [ + "rw.retrieval-debug", + "rw.lifecycle-staleness", + "rw.resume-evidence" + ], + "primary_references": [ + "docs/guide/research/comparison_external_projects.md", + "apps/elf-eval/fixtures/real_world_external_adapters/memory_projects_manifest.json" + ], + "coverage_evidence": [ + { + "label": "retrieval-debug baseline", + "path": "docs/guide/research/comparison_external_projects.md", + "summary": "qmd is the strongest local retrieval-debug reference and has targeted live real-world adapter evidence." + } + ], + "last_seen": { + "observed_at": "2026-06-10T08:32:00.790878Z", + "source_url": "https://github.com/tobi/qmd", + "default_branch": "main", + "pushed_at": "2026-06-08T16:50:52Z", + "updated_at": "2026-06-10T08:26:53Z", + "latest_release": { + "tag_name": "v2.5.3", + "url": "https://github.com/tobi/qmd/releases/tag/v2.5.3", + "published_at": "2026-05-29T03:24:20Z" + }, + "stars": 26365, + "open_issues": 124, + "description": "mini cli search engine for your docs, knowledge bases, meeting notes, whatever. Tracking current sota approaches while being all local" + } + }, + { + "id": "claude-mem", + "name": "claude-mem", + "repo": "thedotmack/claude-mem", + "homepage": "https://github.com/thedotmack/claude-mem", + "watch_focus": [ + "rw.operator-continuity", + "rw.resume-evidence", + "rw.retrieval-debug" + ], + "primary_references": [ + "docs/guide/research/comparison_external_projects.md", + "docs/research/2026-06-09-xy-841-external-memory-benchmark-dimensions.json" + ], + "coverage_evidence": [ + { + "label": "progressive disclosure UX reference", + "path": "docs/guide/research/comparison_external_projects.md", + "summary": "claude-mem remains a product reference for progressive disclosure and viewer workflow, not a proven ELF replacement." + } + ], + "last_seen": { + "observed_at": "2026-06-10T08:32:00.790878Z", + "source_url": "https://github.com/thedotmack/claude-mem", + "default_branch": "main", + "pushed_at": "2026-06-10T07:22:33Z", + "updated_at": "2026-06-10T08:26:21Z", + "latest_release": { + "tag_name": "v13.5.4", + "url": "https://github.com/thedotmack/claude-mem/releases/tag/v13.5.4", + "published_at": "2026-06-10T07:22:17Z" + }, + "stars": 81523, + "open_issues": 80, + "description": "Persistent Context Across Sessions for Every Agent – Captures everything your agent does during sessions, compresses it with AI, and injects relevant context back into future sessions. Works with Claude Code, OpenClaw, Codex, Gemini, Hermes, Copilot, OpenCode + More" + } + }, + { + "id": "openviking", + "name": "OpenViking", + "repo": "volcengine/OpenViking", + "homepage": "https://github.com/volcengine/OpenViking", + "watch_focus": [ + "rw.context-trajectory", + "rw.resume-evidence", + "rw.retrieval-debug" + ], + "primary_references": [ + "docs/guide/research/comparison_external_projects.md", + "apps/elf-eval/fixtures/real_world_external_adapters/memory_projects_manifest.json" + ], + "coverage_evidence": [ + { + "label": "trajectory reference", + "path": "docs/guide/research/comparison_external_projects.md", + "summary": "OpenViking informs hierarchical context trajectory while current adapter evidence remains incomplete." + } + ], + "last_seen": { + "observed_at": "2026-06-10T08:32:00.790878Z", + "source_url": "https://github.com/volcengine/OpenViking", + "default_branch": "main", + "pushed_at": "2026-06-10T08:29:16Z", + "updated_at": "2026-06-10T08:29:49Z", + "latest_release": { + "tag_name": "v0.3.24", + "url": "https://github.com/volcengine/OpenViking/releases/tag/v0.3.24", + "published_at": "2026-06-05T08:05:34Z" + }, + "stars": 25438, + "open_issues": 221, + "description": "OpenViking is an open-source context database designed specifically for AI Agents(such as openclaw). OpenViking unifies the management of context (memory, resources, and skills) that Agents need through a file system paradigm, enabling hierarchical context delivery and self-evolving." + } + }, + { + "id": "graphiti", + "name": "Graphiti / Zep", + "repo": "getzep/graphiti", + "homepage": "https://github.com/getzep/graphiti", + "watch_focus": [ + "rw.graph-temporal", + "rw.resume-evidence" + ], + "primary_references": [ + "docs/guide/research/comparison_external_projects.md", + "apps/elf-eval/fixtures/real_world_external_adapters/memory_projects_manifest.json" + ], + "coverage_evidence": [ + { + "label": "temporal graph reference", + "path": "docs/guide/research/comparison_external_projects.md", + "summary": "Graphiti/Zep remains the broader temporal graph workflow reference for current-versus-historical facts." + } + ], + "last_seen": { + "observed_at": "2026-06-10T08:32:00.790878Z", + "source_url": "https://github.com/getzep/graphiti", + "default_branch": "main", + "pushed_at": "2026-06-10T07:19:57Z", + "updated_at": "2026-06-10T08:29:29Z", + "latest_release": { + "tag_name": "v0.29.2", + "url": "https://github.com/getzep/graphiti/releases/tag/v0.29.2", + "published_at": "2026-06-08T14:25:35Z" + }, + "stars": 27240, + "open_issues": 365, + "description": "Build Real-Time Knowledge Graphs for AI Agents" + } + }, + { + "id": "letta", + "name": "Letta", + "repo": "letta-ai/letta", + "homepage": "https://github.com/letta-ai/letta", + "watch_focus": [ + "rw.core-archival", + "rw.operator-continuity" + ], + "primary_references": [ + "docs/guide/research/comparison_external_projects.md", + "apps/elf-eval/fixtures/real_world_external_adapters/memory_projects_manifest.json" + ], + "coverage_evidence": [ + { + "label": "core versus archival memory reference", + "path": "docs/guide/research/comparison_external_projects.md", + "summary": "Letta informs core memory block ergonomics while ELF keeps archival notes source-of-truth bound." + } + ], + "last_seen": { + "observed_at": "2026-06-10T08:32:00.790878Z", + "source_url": "https://github.com/letta-ai/letta", + "default_branch": "main", + "pushed_at": "2026-05-14T17:14:23Z", + "updated_at": "2026-06-10T08:26:18Z", + "latest_release": { + "tag_name": "0.16.8", + "url": "https://github.com/letta-ai/letta/releases/tag/0.16.8", + "published_at": "2026-05-14T17:14:24Z" + }, + "stars": 23232, + "open_issues": 52, + "description": "Letta is the platform for building stateful agents: AI with advanced memory that can learn and self-improve over time." + } + }, + { + "id": "lightrag", + "name": "LightRAG", + "repo": "HKUDS/LightRAG", + "homepage": "https://github.com/HKUDS/LightRAG", + "watch_focus": [ + "rw.graph-navigation", + "rw.graph-temporal", + "rw.retrieval-debug" + ], + "primary_references": [ + "docs/guide/research/research_projects_inventory.md", + "apps/elf-eval/fixtures/real_world_external_adapters/memory_projects_manifest.json" + ], + "coverage_evidence": [ + { + "label": "research gate", + "path": "docs/guide/research/research_projects_inventory.md", + "summary": "LightRAG is a D0 watch item with a research gate; no adapter strength claim is allowed yet." + } + ], + "last_seen": { + "observed_at": "2026-06-10T08:32:00.790878Z", + "source_url": "https://github.com/HKUDS/LightRAG", + "default_branch": "main", + "pushed_at": "2026-06-09T11:24:04Z", + "updated_at": "2026-06-10T08:28:11Z", + "latest_release": { + "tag_name": "v1.5.1", + "url": "https://github.com/HKUDS/LightRAG/releases/tag/v1.5.1", + "published_at": "2026-06-09T08:32:30Z" + }, + "stars": 36379, + "open_issues": 227, + "description": "[EMNLP2025] \"LightRAG: Simple and Fast Retrieval-Augmented Generation\"" + } + }, + { + "id": "graphrag", + "name": "GraphRAG", + "repo": "microsoft/graphrag", + "homepage": "https://github.com/microsoft/graphrag", + "watch_focus": [ + "rw.graph-navigation", + "rw.knowledge-synthesis", + "rw.retrieval-debug" + ], + "primary_references": [ + "docs/guide/research/research_projects_inventory.md", + "apps/elf-eval/fixtures/real_world_external_adapters/memory_projects_manifest.json" + ], + "coverage_evidence": [ + { + "label": "research gate", + "path": "docs/guide/research/research_projects_inventory.md", + "summary": "GraphRAG is a D0 watch item with a research gate; no adapter strength claim is allowed yet." + } + ], + "last_seen": { + "observed_at": "2026-06-10T08:32:00.790878Z", + "source_url": "https://github.com/microsoft/graphrag", + "default_branch": "main", + "pushed_at": "2026-06-05T23:46:49Z", + "updated_at": "2026-06-10T08:27:19Z", + "latest_release": { + "tag_name": "v3.1.0", + "url": "https://github.com/microsoft/graphrag/releases/tag/v3.1.0", + "published_at": "2026-05-28T15:55:40Z" + }, + "stars": 33610, + "open_issues": 141, + "description": "A modular graph-based Retrieval-Augmented Generation (RAG) system" + } + }, + { + "id": "ragflow", + "name": "RAGFlow", + "repo": "infiniflow/ragflow", + "homepage": "https://github.com/infiniflow/ragflow", + "watch_focus": [ + "rw.resume-evidence", + "rw.graph-navigation", + "rw.retrieval-debug" + ], + "primary_references": [ + "docs/guide/research/research_projects_inventory.md", + "apps/elf-eval/fixtures/real_world_external_adapters/memory_projects_manifest.json" + ], + "coverage_evidence": [ + { + "label": "research gate", + "path": "docs/guide/research/research_projects_inventory.md", + "summary": "RAGFlow is a D0 watch item with a research gate; no adapter strength claim is allowed yet." + } + ], + "last_seen": { + "observed_at": "2026-06-10T08:32:00.790878Z", + "source_url": "https://github.com/infiniflow/ragflow", + "default_branch": "main", + "pushed_at": "2026-06-10T08:09:36Z", + "updated_at": "2026-06-10T08:29:00Z", + "latest_release": { + "tag_name": "v0.25.6", + "url": "https://github.com/infiniflow/ragflow/releases/tag/v0.25.6", + "published_at": "2026-05-27T01:50:19Z" + }, + "stars": 82363, + "open_issues": 3360, + "description": "RAGFlow is a leading open-source Retrieval-Augmented Generation (RAG) engine that fuses cutting-edge RAG with Agent capabilities to create a superior context layer for LLMs" + } + }, + { + "id": "memsearch", + "name": "memsearch", + "repo": "zilliztech/memsearch", + "homepage": "https://github.com/zilliztech/memsearch", + "watch_focus": [ + "rw.lifecycle-staleness", + "rw.retrieval-debug", + "rw.resume-evidence" + ], + "primary_references": [ + "docs/guide/research/comparison_external_projects.md" + ], + "coverage_evidence": [ + { + "label": "markdown-first reference", + "path": "docs/guide/research/comparison_external_projects.md", + "summary": "memsearch remains a source-transparency reference while current adapter evidence is incomplete or wrong-result typed." + } + ], + "last_seen": { + "observed_at": "2026-06-10T08:32:00.790878Z", + "source_url": "https://github.com/zilliztech/memsearch", + "default_branch": "main", + "pushed_at": "2026-06-01T12:52:06Z", + "updated_at": "2026-06-10T08:11:17Z", + "latest_release": { + "tag_name": "v0.4.6", + "url": "https://github.com/zilliztech/memsearch/releases/tag/v0.4.6", + "published_at": "2026-05-29T07:28:49Z" + }, + "stars": 1955, + "open_issues": 219, + "description": "A persistent, unified memory layer for all your AI agents (e.g. Claude Code, Codex), backed by Markdown and Milvus." + } + }, + { + "id": "langgraph", + "name": "LangGraph", + "repo": "langchain-ai/langgraph", + "homepage": "https://github.com/langchain-ai/langgraph", + "watch_focus": [ + "rw.replay-regression", + "rw.resume-evidence" + ], + "primary_references": [ + "docs/guide/research/comparison_external_projects.md" + ], + "coverage_evidence": [ + { + "label": "replay regression reference", + "path": "docs/guide/research/comparison_external_projects.md", + "summary": "LangGraph informs replay and checkpoint regression workflows; ELF traces do not replace full agent-state replay." + } + ], + "last_seen": { + "observed_at": "2026-06-10T08:32:00.790878Z", + "source_url": "https://github.com/langchain-ai/langgraph", + "default_branch": "main", + "pushed_at": "2026-06-09T22:41:05Z", + "updated_at": "2026-06-10T08:30:43Z", + "latest_release": { + "tag_name": "1.2.4", + "url": "https://github.com/langchain-ai/langgraph/releases/tag/1.2.4", + "published_at": "2026-06-02T17:07:49Z" + }, + "stars": 34333, + "open_issues": 560, + "description": "Build resilient agents." + } + }, + { + "id": "nanograph", + "name": "nanograph", + "repo": "nanograph/nanograph", + "homepage": "https://github.com/nanograph/nanograph", + "watch_focus": [ + "rw.graph-temporal", + "rw.retrieval-debug" + ], + "primary_references": [ + "docs/guide/research/comparison_external_projects.md", + "apps/elf-eval/fixtures/real_world_external_adapters/memory_projects_manifest.json" + ], + "coverage_evidence": [ + { + "label": "typed graph ergonomics reference", + "path": "docs/guide/research/comparison_external_projects.md", + "summary": "nanograph is a typed graph DX reference, not a full memory backend benchmark claim." + } + ], + "last_seen": { + "observed_at": "2026-06-10T08:32:00.790878Z", + "source_url": "https://github.com/nanograph/nanograph", + "default_branch": "main", + "pushed_at": "2026-05-17T01:49:29Z", + "updated_at": "2026-06-10T01:45:26Z", + "latest_release": { + "tag_name": "v1.3.0", + "url": "https://github.com/nanograph/nanograph/releases/tag/v1.3.0", + "published_at": "2026-05-16T23:25:46Z" + }, + "stars": 150, + "open_issues": 0, + "description": "On-device property graph database. Schema-as-code. One CLI → One Folder. No Server. Think: DuckDB for graphs." + } + }, + { + "id": "llm-wiki", + "name": "llm-wiki", + "repo": "nvk/llm-wiki", + "homepage": "https://github.com/nvk/llm-wiki", + "watch_focus": [ + "rw.knowledge-synthesis", + "rw.resume-evidence" + ], + "primary_references": [ + "docs/guide/research/comparison_external_projects.md" + ], + "coverage_evidence": [ + { + "label": "derived knowledge pages reference", + "path": "docs/guide/research/comparison_external_projects.md", + "summary": "llm-wiki informs rebuildable cited knowledge pages and lint/repair loops." + } + ], + "last_seen": { + "observed_at": "2026-06-10T08:32:00.790878Z", + "source_url": "https://github.com/nvk/llm-wiki", + "default_branch": "master", + "pushed_at": "2026-05-23T16:07:33Z", + "updated_at": "2026-06-09T16:24:54Z", + "latest_release": { + "tag_name": "v0.10.2", + "url": "https://github.com/nvk/llm-wiki/releases/tag/v0.10.2", + "published_at": "2026-05-23T16:07:33Z" + }, + "stars": 549, + "open_issues": 3, + "description": "LLM-compiled knowledge bases for any AI agent. Parallel multi-agent research, thesis-driven investigation, source ingestion, wiki compilation, querying, and artifact generation. " + } + }, + { + "id": "gbrain", + "name": "gbrain", + "repo": "garrytan/gbrain", + "homepage": "https://github.com/garrytan/gbrain", + "watch_focus": [ + "rw.knowledge-synthesis", + "rw.operator-continuity" + ], + "primary_references": [ + "docs/guide/research/comparison_external_projects.md" + ], + "coverage_evidence": [ + { + "label": "operational brain reference", + "path": "docs/guide/research/comparison_external_projects.md", + "summary": "gbrain informs current-truth and timeline presentation while ELF source notes remain authoritative." + } + ], + "last_seen": { + "observed_at": "2026-06-10T08:32:00.790878Z", + "source_url": "https://github.com/garrytan/gbrain", + "default_branch": "master", + "pushed_at": "2026-06-10T05:32:26Z", + "updated_at": "2026-06-10T08:19:11Z", + "latest_release": null, + "stars": 21971, + "open_issues": 740, + "description": "Garry's Opinionated OpenClaw/Hermes Agent Brain" + } + }, + { + "id": "graphify", + "name": "graphify", + "repo": "safishamsi/graphify", + "homepage": "https://github.com/safishamsi/graphify", + "watch_focus": [ + "rw.graph-navigation", + "rw.knowledge-synthesis", + "rw.resume-evidence" + ], + "primary_references": [ + "docs/guide/research/comparison_external_projects.md" + ], + "coverage_evidence": [ + { + "label": "graph-compressed navigation reference", + "path": "docs/guide/research/comparison_external_projects.md", + "summary": "graphify informs rebuildable graph reports and pre-search guidance without replacing ELF storage." + } + ], + "last_seen": { + "observed_at": "2026-06-10T08:32:00.790878Z", + "source_url": "https://github.com/safishamsi/graphify", + "default_branch": "v8", + "pushed_at": "2026-06-08T22:58:46Z", + "updated_at": "2026-06-10T08:28:45Z", + "latest_release": { + "tag_name": "v0.8.36", + "url": "https://github.com/safishamsi/graphify/releases/tag/v0.8.36", + "published_at": "2026-06-08T22:58:46Z" + }, + "stars": 64475, + "open_issues": 330, + "description": "AI coding assistant skill (Claude Code, Codex, OpenCode, Cursor, Gemini CLI, and more). Turn any folder of code, SQL schemas, R scripts, shell scripts, docs, papers, images, or videos into a queryable knowledge graph. App code + database schema + infrastructure in one graph." + } + } + ], + "last_run": { + "schema": "elf.external_memory_pattern_radar_run/v1", + "run_id": "external-memory-pattern-radar-2026-06-10", + "generated_at": "2026-06-10T08:32:00.790878Z", + "mode": "live", + "summary": { + "project_count": 16, + "covered_count": 16, + "rejected_count": 0, + "gap_count": 0, + "create_issue_count": 0, + "defer_count": 0, + "no_issue_count": 16 + }, + "decisions": [ + { + "project_id": "agentmemory", + "upstream_change": "No GitHub metadata delta was observed since the prior cursor.", + "reusable_pattern": "No new candidate pattern was identified in this run.", + "elf_verdict": "covered", + "product_value": "Current ELF coverage remains represented by the comparison and inventory evidence.", + "duplicate_coverage_evidence": [ + { + "label": "adapter evidence boundary", + "path": "docs/guide/research/comparison_external_projects.md", + "summary": "agentmemory is tracked for operator continuity and resume evidence, but current benchmark evidence does not prove durable lifecycle quality." + } + ], + "safety_boundary": "No external runtime is adopted by default; existing ELF evidence remains authoritative.", + "issue_decision": { + "action": "no_issue", + "rationale": "No issue was created because the run found no source-backed gap.", + "duplicate_search": { + "queried": false, + "query": "", + "result": "not_required_no_issue", + "evidence": [ + "No Linear search is required when the issue decision is no_issue." + ] + }, + "proposed_issue": null + }, + "acceptance_evidence": [ + "No-issue decision recorded in the cursor.", + "Coverage evidence points at checked-in ELF research docs." + ], + "source_links": [ + "https://github.com/rohitg00/agentmemory", + "https://github.com/rohitg00/agentmemory/releases/tag/v0.9.27" + ] + }, + { + "project_id": "mem0", + "upstream_change": "No GitHub metadata delta was observed since the prior cursor.", + "reusable_pattern": "No new candidate pattern was identified in this run.", + "elf_verdict": "covered", + "product_value": "Current ELF coverage remains represented by the comparison and inventory evidence.", + "duplicate_coverage_evidence": [ + { + "label": "lifecycle and graph reference", + "path": "docs/guide/research/comparison_external_projects.md", + "summary": "mem0 remains the ecosystem and entity-scoped lifecycle reference while ELF keeps deterministic evidence-bound writes." + } + ], + "safety_boundary": "No external runtime is adopted by default; existing ELF evidence remains authoritative.", + "issue_decision": { + "action": "no_issue", + "rationale": "No issue was created because the run found no source-backed gap.", + "duplicate_search": { + "queried": false, + "query": "", + "result": "not_required_no_issue", + "evidence": [ + "No Linear search is required when the issue decision is no_issue." + ] + }, + "proposed_issue": null + }, + "acceptance_evidence": [ + "No-issue decision recorded in the cursor.", + "Coverage evidence points at checked-in ELF research docs." + ], + "source_links": [ + "https://github.com/mem0ai/mem0", + "https://github.com/mem0ai/mem0/releases/tag/cli-node-v0.2.8" + ] + }, + { + "project_id": "qmd", + "upstream_change": "No GitHub metadata delta was observed since the prior cursor.", + "reusable_pattern": "No new candidate pattern was identified in this run.", + "elf_verdict": "covered", + "product_value": "Current ELF coverage remains represented by the comparison and inventory evidence.", + "duplicate_coverage_evidence": [ + { + "label": "retrieval-debug baseline", + "path": "docs/guide/research/comparison_external_projects.md", + "summary": "qmd is the strongest local retrieval-debug reference and has targeted live real-world adapter evidence." + } + ], + "safety_boundary": "No external runtime is adopted by default; existing ELF evidence remains authoritative.", + "issue_decision": { + "action": "no_issue", + "rationale": "No issue was created because the run found no source-backed gap.", + "duplicate_search": { + "queried": false, + "query": "", + "result": "not_required_no_issue", + "evidence": [ + "No Linear search is required when the issue decision is no_issue." + ] + }, + "proposed_issue": null + }, + "acceptance_evidence": [ + "No-issue decision recorded in the cursor.", + "Coverage evidence points at checked-in ELF research docs." + ], + "source_links": [ + "https://github.com/tobi/qmd", + "https://github.com/tobi/qmd/releases/tag/v2.5.3" + ] + }, + { + "project_id": "claude-mem", + "upstream_change": "No GitHub metadata delta was observed since the prior cursor.", + "reusable_pattern": "No new candidate pattern was identified in this run.", + "elf_verdict": "covered", + "product_value": "Current ELF coverage remains represented by the comparison and inventory evidence.", + "duplicate_coverage_evidence": [ + { + "label": "progressive disclosure UX reference", + "path": "docs/guide/research/comparison_external_projects.md", + "summary": "claude-mem remains a product reference for progressive disclosure and viewer workflow, not a proven ELF replacement." + } + ], + "safety_boundary": "No external runtime is adopted by default; existing ELF evidence remains authoritative.", + "issue_decision": { + "action": "no_issue", + "rationale": "No issue was created because the run found no source-backed gap.", + "duplicate_search": { + "queried": false, + "query": "", + "result": "not_required_no_issue", + "evidence": [ + "No Linear search is required when the issue decision is no_issue." + ] + }, + "proposed_issue": null + }, + "acceptance_evidence": [ + "No-issue decision recorded in the cursor.", + "Coverage evidence points at checked-in ELF research docs." + ], + "source_links": [ + "https://github.com/thedotmack/claude-mem", + "https://github.com/thedotmack/claude-mem/releases/tag/v13.5.4" + ] + }, + { + "project_id": "openviking", + "upstream_change": "No GitHub metadata delta was observed since the prior cursor.", + "reusable_pattern": "No new candidate pattern was identified in this run.", + "elf_verdict": "covered", + "product_value": "Current ELF coverage remains represented by the comparison and inventory evidence.", + "duplicate_coverage_evidence": [ + { + "label": "trajectory reference", + "path": "docs/guide/research/comparison_external_projects.md", + "summary": "OpenViking informs hierarchical context trajectory while current adapter evidence remains incomplete." + } + ], + "safety_boundary": "No external runtime is adopted by default; existing ELF evidence remains authoritative.", + "issue_decision": { + "action": "no_issue", + "rationale": "No issue was created because the run found no source-backed gap.", + "duplicate_search": { + "queried": false, + "query": "", + "result": "not_required_no_issue", + "evidence": [ + "No Linear search is required when the issue decision is no_issue." + ] + }, + "proposed_issue": null + }, + "acceptance_evidence": [ + "No-issue decision recorded in the cursor.", + "Coverage evidence points at checked-in ELF research docs." + ], + "source_links": [ + "https://github.com/volcengine/OpenViking", + "https://github.com/volcengine/OpenViking/releases/tag/v0.3.24" + ] + }, + { + "project_id": "graphiti", + "upstream_change": "No GitHub metadata delta was observed since the prior cursor.", + "reusable_pattern": "No new candidate pattern was identified in this run.", + "elf_verdict": "covered", + "product_value": "Current ELF coverage remains represented by the comparison and inventory evidence.", + "duplicate_coverage_evidence": [ + { + "label": "temporal graph reference", + "path": "docs/guide/research/comparison_external_projects.md", + "summary": "Graphiti/Zep remains the broader temporal graph workflow reference for current-versus-historical facts." + } + ], + "safety_boundary": "No external runtime is adopted by default; existing ELF evidence remains authoritative.", + "issue_decision": { + "action": "no_issue", + "rationale": "No issue was created because the run found no source-backed gap.", + "duplicate_search": { + "queried": false, + "query": "", + "result": "not_required_no_issue", + "evidence": [ + "No Linear search is required when the issue decision is no_issue." + ] + }, + "proposed_issue": null + }, + "acceptance_evidence": [ + "No-issue decision recorded in the cursor.", + "Coverage evidence points at checked-in ELF research docs." + ], + "source_links": [ + "https://github.com/getzep/graphiti", + "https://github.com/getzep/graphiti/releases/tag/v0.29.2" + ] + }, + { + "project_id": "letta", + "upstream_change": "No GitHub metadata delta was observed since the prior cursor.", + "reusable_pattern": "No new candidate pattern was identified in this run.", + "elf_verdict": "covered", + "product_value": "Current ELF coverage remains represented by the comparison and inventory evidence.", + "duplicate_coverage_evidence": [ + { + "label": "core versus archival memory reference", + "path": "docs/guide/research/comparison_external_projects.md", + "summary": "Letta informs core memory block ergonomics while ELF keeps archival notes source-of-truth bound." + } + ], + "safety_boundary": "No external runtime is adopted by default; existing ELF evidence remains authoritative.", + "issue_decision": { + "action": "no_issue", + "rationale": "No issue was created because the run found no source-backed gap.", + "duplicate_search": { + "queried": false, + "query": "", + "result": "not_required_no_issue", + "evidence": [ + "No Linear search is required when the issue decision is no_issue." + ] + }, + "proposed_issue": null + }, + "acceptance_evidence": [ + "No-issue decision recorded in the cursor.", + "Coverage evidence points at checked-in ELF research docs." + ], + "source_links": [ + "https://github.com/letta-ai/letta", + "https://github.com/letta-ai/letta/releases/tag/0.16.8" + ] + }, + { + "project_id": "lightrag", + "upstream_change": "No GitHub metadata delta was observed since the prior cursor.", + "reusable_pattern": "No new candidate pattern was identified in this run.", + "elf_verdict": "covered", + "product_value": "Current ELF coverage remains represented by the comparison and inventory evidence.", + "duplicate_coverage_evidence": [ + { + "label": "research gate", + "path": "docs/guide/research/research_projects_inventory.md", + "summary": "LightRAG is a D0 watch item with a research gate; no adapter strength claim is allowed yet." + } + ], + "safety_boundary": "No external runtime is adopted by default; existing ELF evidence remains authoritative.", + "issue_decision": { + "action": "no_issue", + "rationale": "No issue was created because the run found no source-backed gap.", + "duplicate_search": { + "queried": false, + "query": "", + "result": "not_required_no_issue", + "evidence": [ + "No Linear search is required when the issue decision is no_issue." + ] + }, + "proposed_issue": null + }, + "acceptance_evidence": [ + "No-issue decision recorded in the cursor.", + "Coverage evidence points at checked-in ELF research docs." + ], + "source_links": [ + "https://github.com/HKUDS/LightRAG", + "https://github.com/HKUDS/LightRAG/releases/tag/v1.5.1" + ] + }, + { + "project_id": "graphrag", + "upstream_change": "No GitHub metadata delta was observed since the prior cursor.", + "reusable_pattern": "No new candidate pattern was identified in this run.", + "elf_verdict": "covered", + "product_value": "Current ELF coverage remains represented by the comparison and inventory evidence.", + "duplicate_coverage_evidence": [ + { + "label": "research gate", + "path": "docs/guide/research/research_projects_inventory.md", + "summary": "GraphRAG is a D0 watch item with a research gate; no adapter strength claim is allowed yet." + } + ], + "safety_boundary": "No external runtime is adopted by default; existing ELF evidence remains authoritative.", + "issue_decision": { + "action": "no_issue", + "rationale": "No issue was created because the run found no source-backed gap.", + "duplicate_search": { + "queried": false, + "query": "", + "result": "not_required_no_issue", + "evidence": [ + "No Linear search is required when the issue decision is no_issue." + ] + }, + "proposed_issue": null + }, + "acceptance_evidence": [ + "No-issue decision recorded in the cursor.", + "Coverage evidence points at checked-in ELF research docs." + ], + "source_links": [ + "https://github.com/microsoft/graphrag", + "https://github.com/microsoft/graphrag/releases/tag/v3.1.0" + ] + }, + { + "project_id": "ragflow", + "upstream_change": "No GitHub metadata delta was observed since the prior cursor.", + "reusable_pattern": "No new candidate pattern was identified in this run.", + "elf_verdict": "covered", + "product_value": "Current ELF coverage remains represented by the comparison and inventory evidence.", + "duplicate_coverage_evidence": [ + { + "label": "research gate", + "path": "docs/guide/research/research_projects_inventory.md", + "summary": "RAGFlow is a D0 watch item with a research gate; no adapter strength claim is allowed yet." + } + ], + "safety_boundary": "No external runtime is adopted by default; existing ELF evidence remains authoritative.", + "issue_decision": { + "action": "no_issue", + "rationale": "No issue was created because the run found no source-backed gap.", + "duplicate_search": { + "queried": false, + "query": "", + "result": "not_required_no_issue", + "evidence": [ + "No Linear search is required when the issue decision is no_issue." + ] + }, + "proposed_issue": null + }, + "acceptance_evidence": [ + "No-issue decision recorded in the cursor.", + "Coverage evidence points at checked-in ELF research docs." + ], + "source_links": [ + "https://github.com/infiniflow/ragflow", + "https://github.com/infiniflow/ragflow/releases/tag/v0.25.6" + ] + }, + { + "project_id": "memsearch", + "upstream_change": "No GitHub metadata delta was observed since the prior cursor.", + "reusable_pattern": "No new candidate pattern was identified in this run.", + "elf_verdict": "covered", + "product_value": "Current ELF coverage remains represented by the comparison and inventory evidence.", + "duplicate_coverage_evidence": [ + { + "label": "markdown-first reference", + "path": "docs/guide/research/comparison_external_projects.md", + "summary": "memsearch remains a source-transparency reference while current adapter evidence is incomplete or wrong-result typed." + } + ], + "safety_boundary": "No external runtime is adopted by default; existing ELF evidence remains authoritative.", + "issue_decision": { + "action": "no_issue", + "rationale": "No issue was created because the run found no source-backed gap.", + "duplicate_search": { + "queried": false, + "query": "", + "result": "not_required_no_issue", + "evidence": [ + "No Linear search is required when the issue decision is no_issue." + ] + }, + "proposed_issue": null + }, + "acceptance_evidence": [ + "No-issue decision recorded in the cursor.", + "Coverage evidence points at checked-in ELF research docs." + ], + "source_links": [ + "https://github.com/zilliztech/memsearch", + "https://github.com/zilliztech/memsearch/releases/tag/v0.4.6" + ] + }, + { + "project_id": "langgraph", + "upstream_change": "No GitHub metadata delta was observed since the prior cursor.", + "reusable_pattern": "No new candidate pattern was identified in this run.", + "elf_verdict": "covered", + "product_value": "Current ELF coverage remains represented by the comparison and inventory evidence.", + "duplicate_coverage_evidence": [ + { + "label": "replay regression reference", + "path": "docs/guide/research/comparison_external_projects.md", + "summary": "LangGraph informs replay and checkpoint regression workflows; ELF traces do not replace full agent-state replay." + } + ], + "safety_boundary": "No external runtime is adopted by default; existing ELF evidence remains authoritative.", + "issue_decision": { + "action": "no_issue", + "rationale": "No issue was created because the run found no source-backed gap.", + "duplicate_search": { + "queried": false, + "query": "", + "result": "not_required_no_issue", + "evidence": [ + "No Linear search is required when the issue decision is no_issue." + ] + }, + "proposed_issue": null + }, + "acceptance_evidence": [ + "No-issue decision recorded in the cursor.", + "Coverage evidence points at checked-in ELF research docs." + ], + "source_links": [ + "https://github.com/langchain-ai/langgraph", + "https://github.com/langchain-ai/langgraph/releases/tag/1.2.4" + ] + }, + { + "project_id": "nanograph", + "upstream_change": "No GitHub metadata delta was observed since the prior cursor.", + "reusable_pattern": "No new candidate pattern was identified in this run.", + "elf_verdict": "covered", + "product_value": "Current ELF coverage remains represented by the comparison and inventory evidence.", + "duplicate_coverage_evidence": [ + { + "label": "typed graph ergonomics reference", + "path": "docs/guide/research/comparison_external_projects.md", + "summary": "nanograph is a typed graph DX reference, not a full memory backend benchmark claim." + } + ], + "safety_boundary": "No external runtime is adopted by default; existing ELF evidence remains authoritative.", + "issue_decision": { + "action": "no_issue", + "rationale": "No issue was created because the run found no source-backed gap.", + "duplicate_search": { + "queried": false, + "query": "", + "result": "not_required_no_issue", + "evidence": [ + "No Linear search is required when the issue decision is no_issue." + ] + }, + "proposed_issue": null + }, + "acceptance_evidence": [ + "No-issue decision recorded in the cursor.", + "Coverage evidence points at checked-in ELF research docs." + ], + "source_links": [ + "https://github.com/nanograph/nanograph", + "https://github.com/nanograph/nanograph/releases/tag/v1.3.0" + ] + }, + { + "project_id": "llm-wiki", + "upstream_change": "No GitHub metadata delta was observed since the prior cursor.", + "reusable_pattern": "No new candidate pattern was identified in this run.", + "elf_verdict": "covered", + "product_value": "Current ELF coverage remains represented by the comparison and inventory evidence.", + "duplicate_coverage_evidence": [ + { + "label": "derived knowledge pages reference", + "path": "docs/guide/research/comparison_external_projects.md", + "summary": "llm-wiki informs rebuildable cited knowledge pages and lint/repair loops." + } + ], + "safety_boundary": "No external runtime is adopted by default; existing ELF evidence remains authoritative.", + "issue_decision": { + "action": "no_issue", + "rationale": "No issue was created because the run found no source-backed gap.", + "duplicate_search": { + "queried": false, + "query": "", + "result": "not_required_no_issue", + "evidence": [ + "No Linear search is required when the issue decision is no_issue." + ] + }, + "proposed_issue": null + }, + "acceptance_evidence": [ + "No-issue decision recorded in the cursor.", + "Coverage evidence points at checked-in ELF research docs." + ], + "source_links": [ + "https://github.com/nvk/llm-wiki", + "https://github.com/nvk/llm-wiki/releases/tag/v0.10.2" + ] + }, + { + "project_id": "gbrain", + "upstream_change": "No GitHub metadata delta was observed since the prior cursor.", + "reusable_pattern": "No new candidate pattern was identified in this run.", + "elf_verdict": "covered", + "product_value": "Current ELF coverage remains represented by the comparison and inventory evidence.", + "duplicate_coverage_evidence": [ + { + "label": "operational brain reference", + "path": "docs/guide/research/comparison_external_projects.md", + "summary": "gbrain informs current-truth and timeline presentation while ELF source notes remain authoritative." + } + ], + "safety_boundary": "No external runtime is adopted by default; existing ELF evidence remains authoritative.", + "issue_decision": { + "action": "no_issue", + "rationale": "No issue was created because the run found no source-backed gap.", + "duplicate_search": { + "queried": false, + "query": "", + "result": "not_required_no_issue", + "evidence": [ + "No Linear search is required when the issue decision is no_issue." + ] + }, + "proposed_issue": null + }, + "acceptance_evidence": [ + "No-issue decision recorded in the cursor.", + "Coverage evidence points at checked-in ELF research docs." + ], + "source_links": [ + "https://github.com/garrytan/gbrain" + ] + }, + { + "project_id": "graphify", + "upstream_change": "No GitHub metadata delta was observed since the prior cursor.", + "reusable_pattern": "No new candidate pattern was identified in this run.", + "elf_verdict": "covered", + "product_value": "Current ELF coverage remains represented by the comparison and inventory evidence.", + "duplicate_coverage_evidence": [ + { + "label": "graph-compressed navigation reference", + "path": "docs/guide/research/comparison_external_projects.md", + "summary": "graphify informs rebuildable graph reports and pre-search guidance without replacing ELF storage." + } + ], + "safety_boundary": "No external runtime is adopted by default; existing ELF evidence remains authoritative.", + "issue_decision": { + "action": "no_issue", + "rationale": "No issue was created because the run found no source-backed gap.", + "duplicate_search": { + "queried": false, + "query": "", + "result": "not_required_no_issue", + "evidence": [ + "No Linear search is required when the issue decision is no_issue." + ] + }, + "proposed_issue": null + }, + "acceptance_evidence": [ + "No-issue decision recorded in the cursor.", + "Coverage evidence points at checked-in ELF research docs." + ], + "source_links": [ + "https://github.com/safishamsi/graphify", + "https://github.com/safishamsi/graphify/releases/tag/v0.8.36" + ] + } + ] + } +} diff --git a/docs/research/external_memory_pattern_radar/latest.md b/docs/research/external_memory_pattern_radar/latest.md new file mode 100644 index 00000000..00cb8fa7 --- /dev/null +++ b/docs/research/external_memory_pattern_radar/latest.md @@ -0,0 +1,39 @@ +# External Memory Pattern Radar Summary + +Goal: Preserve the latest weekly ELF external memory pattern radar outcome. +Read this when: Feeding the next full comparison report or deciding whether a watched upstream memory project created an ELF follow-up. +Inputs: `docs/research/external_memory_pattern_radar/cursor.json`, GitHub repository metadata, checked-in ELF comparison evidence, and any Codex source-review notes. +Depends on: `docs/spec/external_memory_pattern_radar_v1.md` and `docs/guide/research/external_memory_pattern_radar.md`. +Outputs: Latest no-issue, rejection, or issue-ready radar decisions. + +- Run id: `external-memory-pattern-radar-2026-06-10` +- Generated at: `2026-06-10T08:32:00.790878Z` +- Mode: `live` +- Projects: `16`; covered: `16`; rejected: `0`; gaps: `0`; create_issue: `0` + +## Decisions + +| Project | Upstream change | ELF verdict | Issue decision | Acceptance evidence | +| --- | --- | --- | --- | --- | +| `agentmemory` | No GitHub metadata delta was observed since the prior cursor. | `covered` | `no_issue` | No-issue decision recorded in the cursor.; Coverage evidence points at checked-in ELF research docs. | +| `mem0` | No GitHub metadata delta was observed since the prior cursor. | `covered` | `no_issue` | No-issue decision recorded in the cursor.; Coverage evidence points at checked-in ELF research docs. | +| `qmd` | No GitHub metadata delta was observed since the prior cursor. | `covered` | `no_issue` | No-issue decision recorded in the cursor.; Coverage evidence points at checked-in ELF research docs. | +| `claude-mem` | No GitHub metadata delta was observed since the prior cursor. | `covered` | `no_issue` | No-issue decision recorded in the cursor.; Coverage evidence points at checked-in ELF research docs. | +| `openviking` | No GitHub metadata delta was observed since the prior cursor. | `covered` | `no_issue` | No-issue decision recorded in the cursor.; Coverage evidence points at checked-in ELF research docs. | +| `graphiti` | No GitHub metadata delta was observed since the prior cursor. | `covered` | `no_issue` | No-issue decision recorded in the cursor.; Coverage evidence points at checked-in ELF research docs. | +| `letta` | No GitHub metadata delta was observed since the prior cursor. | `covered` | `no_issue` | No-issue decision recorded in the cursor.; Coverage evidence points at checked-in ELF research docs. | +| `lightrag` | No GitHub metadata delta was observed since the prior cursor. | `covered` | `no_issue` | No-issue decision recorded in the cursor.; Coverage evidence points at checked-in ELF research docs. | +| `graphrag` | No GitHub metadata delta was observed since the prior cursor. | `covered` | `no_issue` | No-issue decision recorded in the cursor.; Coverage evidence points at checked-in ELF research docs. | +| `ragflow` | No GitHub metadata delta was observed since the prior cursor. | `covered` | `no_issue` | No-issue decision recorded in the cursor.; Coverage evidence points at checked-in ELF research docs. | +| `memsearch` | No GitHub metadata delta was observed since the prior cursor. | `covered` | `no_issue` | No-issue decision recorded in the cursor.; Coverage evidence points at checked-in ELF research docs. | +| `langgraph` | No GitHub metadata delta was observed since the prior cursor. | `covered` | `no_issue` | No-issue decision recorded in the cursor.; Coverage evidence points at checked-in ELF research docs. | +| `nanograph` | No GitHub metadata delta was observed since the prior cursor. | `covered` | `no_issue` | No-issue decision recorded in the cursor.; Coverage evidence points at checked-in ELF research docs. | +| `llm-wiki` | No GitHub metadata delta was observed since the prior cursor. | `covered` | `no_issue` | No-issue decision recorded in the cursor.; Coverage evidence points at checked-in ELF research docs. | +| `gbrain` | No GitHub metadata delta was observed since the prior cursor. | `covered` | `no_issue` | No-issue decision recorded in the cursor.; Coverage evidence points at checked-in ELF research docs. | +| `graphify` | No GitHub metadata delta was observed since the prior cursor. | `covered` | `no_issue` | No-issue decision recorded in the cursor.; Coverage evidence points at checked-in ELF research docs. | + +## Safety Boundary + +- The radar records upstream movement as a trigger for source review, not as proof of parity or a reason to adopt an external runtime. +- `create_issue` decisions are valid only when the cursor includes source links, repo evidence, non-goals, validation criteria, and Linear duplicate-search evidence. +- No-issue runs remain useful because each project records why ELF is already covered or why metadata-only movement was rejected. diff --git a/docs/spec/external_memory_pattern_radar_v1.md b/docs/spec/external_memory_pattern_radar_v1.md new file mode 100644 index 00000000..ccde7b34 --- /dev/null +++ b/docs/spec/external_memory_pattern_radar_v1.md @@ -0,0 +1,118 @@ +# External Memory Pattern Radar v1 + +Purpose: Define the durable cursor, run, and issue-decision contract for ELF's external +memory pattern radar. +Status: normative +Read this when: You are changing the weekly radar runner, cursor file, summary output, +or follow-up issue creation boundary. +Not this document: The current project comparison, benchmark results, or step-by-step +operator runbook. +Defines: `elf.external_memory_pattern_radar_cursor/v1` and +`elf.external_memory_pattern_radar_run/v1`. + +## Goal + +The radar keeps ELF aware of fast-moving memory, RAG, graph-memory, and +agent-continuity systems without weakening ELF's evidence-linked source-of-truth model. + +The radar is a decision-support workflow. It is not an adoption workflow. + +## Artifacts + +Canonical checked-in paths: + +- Cursor: `docs/research/external_memory_pattern_radar/cursor.json` +- Latest prose summary: `docs/research/external_memory_pattern_radar/latest.md` + +Temporary dry-run outputs may be written under `tmp/external-memory-pattern-radar/`. + +## Cursor Schema + +`cursor.json` must use: + +```json +{ + "schema": "elf.external_memory_pattern_radar_cursor/v1", + "cadence": "weekly", + "generated_at": "RFC3339 timestamp", + "source_docs": ["repo-relative path or URL"], + "projects": [], + "last_run": null +} +``` + +Each `projects[]` entry must contain: + +| Field | Type | Requirement | +| --- | --- | --- | +| `id` | string | Stable snake-case or kebab-safe project id. | +| `name` | string | Human-readable project name. | +| `repo` | string | GitHub `owner/name`. | +| `homepage` | string | Primary upstream URL. | +| `watch_focus` | string array | ELF benchmark or product dimensions watched for this project. | +| `primary_references` | string array | Repo-relative docs or source URLs used as current ELF context. | +| `coverage_evidence` | evidence array | Existing ELF evidence for duplicate/coverage checks. | +| `last_seen` | object or null | Last observed GitHub metadata. | + +`coverage_evidence[]` entries must contain `label`, `path`, and `summary`. + +## Run Schema + +`last_run` must use: + +```json +{ + "schema": "elf.external_memory_pattern_radar_run/v1", + "run_id": "string", + "generated_at": "RFC3339 timestamp", + "mode": "live|offline", + "summary": {}, + "decisions": [] +} +``` + +Every run must include one decision per project. + +## Decision Contract + +Every `decisions[]` entry must record: + +| Field | Requirement | +| --- | --- | +| `project_id` | Must match a cursor project id. | +| `upstream_change` | What changed upstream, or why no upstream fetch/change occurred. | +| `reusable_pattern` | Candidate reusable pattern, or why no pattern is claimed. | +| `elf_verdict` | One of `covered`, `reject`, or `gap`. | +| `product_value` | Product value or explicit no-value statement. | +| `duplicate_coverage_evidence` | Existing ELF docs, issues, benchmark records, or code pointers. | +| `safety_boundary` | Boundary preventing unsafe adoption, overclaiming, or hidden runtime changes. | +| `issue_decision` | No-issue, defer, or create-issue decision with rationale. | +| `acceptance_evidence` | Evidence that the radar decision itself met this contract. | +| `source_links` | Upstream links used by the decision. | + +Metadata-only upstream movement must not produce `elf_verdict = "gap"`. Metadata-only +movement may only produce `covered` or `reject`, because stars, push timestamps, and +release tags are review triggers rather than architecture evidence. + +## Issue Creation Boundary + +`issue_decision.action = "create_issue"` is valid only when all of the following are +present in the same decision record: + +- `elf_verdict = "gap"` +- upstream source links +- repo evidence showing the ELF gap or missing coverage +- explicit non-goals +- validation criteria +- Linear duplicate-search evidence with `duplicate_search.queried = true` + +If any item is missing, the decision must be `no_issue` or `defer`. + +## Scheduled Workflow Boundary + +GitHub Actions may refresh metadata and upload read-only artifacts. GitHub Actions must +not make AI source-review judgments, create Linear issues, or claim adoption value from +activity alone. + +Codex or Decodex automation may promote a radar observation into a follow-up issue only +after source review and duplicate search satisfy this spec. diff --git a/docs/spec/index.md b/docs/spec/index.md index 127baf7d..353bb63f 100644 --- a/docs/spec/index.md +++ b/docs/spec/index.md @@ -43,6 +43,8 @@ Question this index answers: "what must remain true?" corpus manifest schema for adoption benchmark runs. - `real_world_agent_memory_benchmark_v1.md`: Real-world agent memory benchmark job schema, suite taxonomy, scoring dimensions, and report state semantics. +- `external_memory_pattern_radar_v1.md`: Weekly external memory pattern radar cursor, + run, decision, and issue-creation boundary schema. ## Spec document contract