Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ The current scope split is:
| Discovery and health | `hosts`, `apps`, `source_ips`, `status`, `stats`, `ingest_rate`, `silent_hosts`, `clock_skew` |
| Analytics and correlation | `timeline`, `patterns`, `anomalies`, `compare`, `correlate`, `topic_correlate`, `similar_incidents`, `recurring_error_comparison`, `incident_context` |
| Fleet and topology | `map`, `host_state`, `fleet_state`, `correlate_state`, `graph`, `compose_status`, `compose_doctor` |
| AI sessions and scoped evidence | `sessions`, `search_sessions`, `evidence_scope`, `abuse`, `abuse_incidents`, `abuse_investigate`, `ai_correlate`, `usage_blocks`, `project_context`, `list_ai_tools`, `list_ai_projects` |
| AI sessions and scoped evidence | `sessions`, `search_sessions`, `session_investigate`, `evidence_scope`, `abuse`, `abuse_incidents`, `abuse_investigate`, `ai_correlate`, `usage_blocks`, `project_context`, `list_ai_tools`, `list_ai_projects` |
| AI operational events | `skill_events`, `skill_incidents`, `skill_investigate`, `mcp_events`, `mcp_incidents`, `mcp_investigate`, `hook_events`, `hook_incidents`, `hook_investigate` |
| Errors and administration | `unaddressed_errors`, `ack_error`, `unack_error`, `notifications_recent`, `notifications_test`, `file_tails`, `llm_invocations`, `artifact_evidence_record` |
| Reference | `help` |
Expand Down
3 changes: 2 additions & 1 deletion docs/INVENTORY.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "Component Inventory -- cortex"
created: 2026-04-04
updated: 2026-09-27
updated: 2026-09-30
---

# Component Inventory -- cortex
Expand Down Expand Up @@ -86,6 +86,7 @@ that registry by `src/mcp/schemas.rs::tool_definitions()`.
| `status` | Lightweight runtime status: DB health, queue/backpressure state, listener/writer counters, OTLP counters | no |
| `sessions` | AI transcript sessions grouped by project/tool/session/host | no |
| `search_sessions` | Ranked grouped session search | no |
| `session_investigate` | Bounded evidence bundle rooted at one AI session | no |
| `evidence_scope` | Historical Agent Observatory evidence for a Git branch or worktree | no |
| `abuse` | Abuse-term detector with same-session context | no |
| `abuse_incidents` | Groups abuse hits into scored incident candidates | no |
Expand Down
6 changes: 4 additions & 2 deletions docs/mcp/SCHEMA.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "Tool Schema Documentation -- cortex"
created: "2026-07-30"
updated: "2026-08-04"
updated: 2026-09-30
---

# Tool Schema Documentation -- cortex
Expand Down Expand Up @@ -45,6 +45,7 @@ selects one of the actions below. The mechanically generated current count is in
| `apps` | `cortex:read` | cheap | Distinct application names with counts |
| `sessions` | `cortex:read` | cheap | AI transcript session inventory |
| `search_sessions` | `cortex:read` | cheap | FTS5 search over AI transcript sessions |
| `session_investigate` | `cortex:read` | expensive | Bounded evidence bundle rooted at one AI session |
| `evidence_scope` | `cortex:read` | moderate | Historical Agent Observatory evidence for a Git branch or worktree |
| `abuse` | `cortex:read` | moderate | Abuse-term hits with same-session context |
| `abuse_incidents` | `cortex:read` | moderate | Grouped abuse incident candidates |
Expand Down Expand Up @@ -175,12 +176,13 @@ boundary.
| `query` | `search`, `search_sessions`, `correlate`, `similar_incidents` |
| `hostname` | `search`, `filter`, `tail`, `correlate`, `host_state`, `ai_correlate`, `apps`, `sessions`, `timeline`, `patterns`, `context`, `similar_incidents`, `incident_context` |
| `host_id` | Authoritative heartbeat identity for `host_state` |
| `host` | Optional host_id-or-hostname filter for `correlate_state` |
| `host` | Optional host_id-or-hostname filter for `correlate_state`; exact session identity qualifier for `session_investigate` |
| `reference_time` | Required window center for `correlate_state`; for `correlate`, required unless `query` is given (then derived from an AI-session search) |
| `source_ip` | `search`, `filter`, `tail`, `correlate`, `ai_correlate` |
| `source_kind` | `filter` only; aliases Docker, file-tail, command-history, shell-history, transcript, and AI-tool rows |
| `project` | `filter`, `sessions`, `search_sessions`, `abuse`, `ai_correlate`, `usage_blocks`, `project_context`, `list_ai_tools` |
| `tool` | `filter`, `sessions`, `search_sessions`, `abuse`, `ai_correlate`, `usage_blocks`, `project_context`, `list_ai_projects` |
| `session_id` | Required for `session_investigate`; optional exact native identity filter for `sessions` |
| `branch`, `worktree` | `evidence_scope`; at least one is required by service validation |
| `kinds`, `include_payload`, `after_id` | `evidence_scope` filtering and durable pagination |
| `session_id` | `filter`, `ai_correlate` |
Expand Down
6 changes: 3 additions & 3 deletions docs/mcp/TESTS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "Testing Guide -- cortex"
created: "2026-07-30"
updated: 2026-09-27
updated: 2026-09-30
---

# Testing Guide -- cortex
Expand Down Expand Up @@ -77,7 +77,7 @@ the repo-local debug binary at `target/debug/cortex`, so repo-local builds do
not require an installed shell binary.

Action registry covered by live/script references: `search`, `filter`, `tail`, `errors`,
`hosts`, `map`, `host_state`, `fleet_state`, `correlate_state`, `topic_correlate`, `sessions`, `search_sessions`, `evidence_scope`, `abuse`, `abuse_incidents`, `abuse_investigate`, `ai_correlate`, `usage_blocks`, `project_context`,
`hosts`, `map`, `host_state`, `fleet_state`, `correlate_state`, `topic_correlate`, `sessions`, `search_sessions`, `session_investigate`, `evidence_scope`, `abuse`, `abuse_incidents`, `abuse_investigate`, `ai_correlate`, `usage_blocks`, `project_context`,
`list_ai_tools`, `list_ai_projects`, `correlate`, `stats`, `status`, `apps`,
`source_ips`, `timeline`, `patterns`, `context`, `get`, `ingest_rate`,
`silent_hosts`, `clock_skew`, `anomalies`, `compare`, `compose_status`,
Expand Down Expand Up @@ -195,7 +195,7 @@ curl -s -X POST http://localhost:3100/mcp \
## Testing checklist

- [ ] **All actions return expected shape** -- cortex search, cortex tail, cortex errors, cortex hosts, cortex host_state, cortex sessions, cortex correlate, cortex stats, cortex status, cortex help
- [ ] **AI session analytics and scoped lifecycle evidence return expected shape and seeded rows** -- cortex search_sessions, cortex evidence_scope, cortex abuse, cortex sessions_correlate, cortex usage_blocks, cortex project_context, cortex list_ai_tools, cortex list_ai_projects
- [ ] **AI session analytics and scoped lifecycle evidence return expected shape and seeded rows** -- cortex search_sessions, cortex session_investigate, cortex evidence_scope, cortex abuse, cortex sessions_correlate, cortex usage_blocks, cortex project_context, cortex list_ai_tools, cortex list_ai_projects
- [ ] **Auth: valid token** -- 200 with correct Bearer token
- [ ] **Auth: invalid token** -- 401 Unauthorized
- [ ] **Auth: no token when required** -- 401 Unauthorized
Expand Down
18 changes: 17 additions & 1 deletion docs/mcp/TOOLS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "MCP Tools Reference -- cortex"
created: "2026-07-30"
updated: "2026-08-04"
updated: 2026-09-30
---

# MCP Tools Reference -- cortex
Expand All @@ -24,6 +24,7 @@ cortex exposes one MCP tool named `cortex`. The required
| `correlate_state` | Correlate logs with heartbeat window summaries around a reference time |
| `sessions` | AI transcript sessions by project |
| `search_sessions` | Ranked grouped session search |
| `session_investigate` | Bounded evidence bundle rooted at one AI session |
| `evidence_scope` | Historical Agent Observatory evidence for a Git branch or worktree |
| `abuse` | Abuse hits in AI transcripts with same-session context |
| `abuse_incidents` | Groups abuse hits into scored incident candidates |
Expand Down Expand Up @@ -182,6 +183,21 @@ Required arguments: `action = "search_sessions"`, `query`

Optional arguments: `project`, `tool`, `from`, `to`, `limit`.

## cortex session_investigate

Build an evidence bundle for one exact AI session. Required arguments:
`action = "session_investigate"`, `session_id`. Optional arguments: `tool`,
`project`, `host`, `limit` (1–200), and `severity_min`. Provide identity qualifiers
when a native session ID is shared by several transcripts.

The response contains `metadata` and `result`, including rendered transcript,
scoped graph correlation, Observatory lineage, tools/skills/hooks, artifacts,
notifications, incidents, and retained deletion lineage. Each section reports
truncation; `partial_reasons` records omitted evidence. The complete JSON envelope
is capped at 64 KiB and wall time at two seconds; exhausted wall time returns a
retryable busy error. Transcript references are passive claims with
`verified = false`.

## cortex evidence_scope

Return a bounded historical page of Agent Observatory events associated with an
Expand Down
2 changes: 1 addition & 1 deletion packages/cortex-rmcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ The current scope split is:
| Discovery and health | `hosts`, `apps`, `source_ips`, `status`, `stats`, `ingest_rate`, `silent_hosts`, `clock_skew` |
| Analytics and correlation | `timeline`, `patterns`, `anomalies`, `compare`, `correlate`, `topic_correlate`, `similar_incidents`, `recurring_error_comparison`, `incident_context` |
| Fleet and topology | `map`, `host_state`, `fleet_state`, `correlate_state`, `graph`, `compose_status`, `compose_doctor` |
| AI sessions and scoped evidence | `sessions`, `search_sessions`, `evidence_scope`, `abuse`, `abuse_incidents`, `abuse_investigate`, `ai_correlate`, `usage_blocks`, `project_context`, `list_ai_tools`, `list_ai_projects` |
| AI sessions and scoped evidence | `sessions`, `search_sessions`, `session_investigate`, `evidence_scope`, `abuse`, `abuse_incidents`, `abuse_investigate`, `ai_correlate`, `usage_blocks`, `project_context`, `list_ai_tools`, `list_ai_projects` |
| AI operational events | `skill_events`, `skill_incidents`, `skill_investigate`, `mcp_events`, `mcp_incidents`, `mcp_investigate`, `hook_events`, `hook_incidents`, `hook_investigate` |
| Errors and administration | `unaddressed_errors`, `ack_error`, `unack_error`, `notifications_recent`, `notifications_test`, `file_tails`, `llm_invocations`, `artifact_evidence_record` |
| Reference | `help` |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ A single MCP tool, `mcp__cortex__cortex`, dispatches on a required `action` argu
| `correlate_state` | Correlate logs with heartbeat summaries around a reference time |
| `sessions` | AI transcript sessions by project |
| `search_sessions` | Ranked grouped session search |
| `session_investigate` | Bounded evidence bundle rooted at one AI session |
| `evidence_scope` | Historical Agent Observatory evidence for a Git branch or worktree |
| `abuse` | Abuse hits in AI transcripts with same-session context |
| `abuse_incidents` | Groups abuse hits into scored incident candidates |
Expand Down
22 changes: 22 additions & 0 deletions scripts/test-agent-memory-symlinks.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ set -euo pipefail

script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
checker="$script_dir/check-agent-memory-symlinks.sh"
# Hooks export repository-local Git variables. A fixture's `git -C ... init`
# must select its own directory rather than reinitialize the live repository.
while IFS= read -r variable; do
unset "$variable"
done < <(git rev-parse --local-env-vars)
fixture="$(mktemp -d)"
trap 'rm -rf "$fixture"' EXIT
git -C "$fixture" init -q
Expand Down Expand Up @@ -98,4 +103,21 @@ expect_fail "force-staged private override"
git -C "$fixture" rm --cached -q AGENTS.override.md
expect_pass "private override removed from index"

if [[ "${CORTEX_INSTRUCTION_FIXTURE_CHILD:-}" != "1" ]]; then
hook_fixture="$fixture/hook-environment"
mkdir "$hook_fixture"
git -C "$hook_fixture" init -q
printf 'Preserve the hook repository index\n' > "$hook_fixture/sentinel"
git -C "$hook_fixture" add sentinel
cp "$hook_fixture/.git/config" "$fixture/config-before"
cp "$hook_fixture/.git/index" "$fixture/index-before"
env CORTEX_INSTRUCTION_FIXTURE_CHILD=1 \
GIT_DIR="$hook_fixture/.git" GIT_WORK_TREE="$hook_fixture" \
GIT_INDEX_FILE="$hook_fixture/.git/index" \
bash "$script_dir/test-agent-memory-symlinks.sh"
cmp "$fixture/config-before" "$hook_fixture/.git/config"
cmp "$fixture/index-before" "$hook_fixture/.git/index"
checks=$((checks + 1))
fi

printf "[agent-memory-test] OK — %s regression cases\n" "$checks"
1 change: 1 addition & 0 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,7 @@ async fn observatory_runs(
since: query.since,
until: query.until,
active_only: query.active_only.unwrap_or(false),
..Default::default()
},
query.cursor,
query.limit.unwrap_or(50),
Expand Down
5 changes: 5 additions & 0 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,11 @@ pub use models::{
ServiceJournalEntry,
ServiceLogsRequest,
ServiceLogsResponse,
SessionExternalReference,
SessionExternalReferenceKind,
SessionInvestigateRequest,
SessionInvestigateResponse,
SessionObservatoryEvidence,
SeverityCount,
SilentHostsRequest,
SilentHostsResponse,
Expand Down
1 change: 1 addition & 0 deletions src/app/models/ai_incidents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,7 @@ pub struct GraphSessionCorrelation {
/// discover related hosts; `false` for the time-windowed fallback (session
/// not yet projected into the graph).
pub used_graph: bool,
pub session_entity_keys: Vec<String>,
pub discovered_hosts: Vec<String>,
pub discovered_entities: Vec<String>,
pub logs: Vec<CorrelatedLogRow>,
Expand Down
1 change: 1 addition & 0 deletions src/app/models/ai_sessions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use super::*;
pub struct ListSessionsRequest {
pub project: Option<String>,
pub tool: Option<String>,
pub session_id: Option<String>,
pub host: Option<String>,
pub since: Option<String>,
pub until: Option<String>,
Expand Down
123 changes: 113 additions & 10 deletions src/app/models/investigation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,109 @@ pub struct AskInvestigationResponse {
pub logs: Vec<AppLogSummary>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct SessionInvestigateRequest {
pub session_id: String,
pub tool: Option<String>,
pub project: Option<String>,
pub host: Option<String>,
pub limit: Option<u32>,
pub severity_min: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
#[serde(rename_all = "snake_case")]
pub enum SessionExternalReferenceKind {
GithubPullRequest,
GithubIssue,
GithubReference,
LinearIssue,
Url,
CommitSha,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub struct SessionExternalReference {
pub kind: SessionExternalReferenceKind,
pub value: String,
pub source_position: Option<i64>,
pub evidence_kind: String,
pub trust_level: String,
pub verified: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionRetentionLineageEntry {
pub log_id: i64,
pub hostname: String,
pub app_name: Option<String>,
pub severity: String,
pub ai_project: Option<String>,
pub ai_tool: Option<String>,
pub ai_session_id: Option<String>,
pub retained_until_epoch: i64,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SessionSourceEvidenceSummary {
pub count: usize,
pub log_ids: Vec<i64>,
pub truncated: bool,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct SessionObservatoryEvidence {
pub runs: Vec<db::agent_observatory::ObservatoryRunRow>,
pub ambiguous_run: bool,
pub parent_run: Option<db::agent_observatory::ObservatoryRunRow>,
pub previous_run: Option<db::agent_observatory::ObservatoryRunRow>,
pub actors: Vec<db::agent_observatory::ObservatoryActorRow>,
pub actors_truncated: bool,
pub related_runs: Vec<db::agent_observatory::ObservatoryRunRow>,
pub related_runs_truncated: bool,
pub repository: Option<db::agent_observatory::ObservatoryRepositoryRow>,
pub worktree: Option<db::agent_observatory::ObservatoryWorktreeRow>,
pub commits: Vec<db::agent_observatory::AgentRunAttributedCommit>,
pub commits_truncated: bool,
pub events: Vec<db::agent_observatory::ObservatoryEventRow>,
pub spans: Vec<db::agent_observatory::ObservatorySpanRow>,
pub metrics: Vec<db::agent_observatory::ObservatoryMetricRow>,
pub events_truncated: bool,
pub spans_truncated: bool,
pub metrics_truncated: bool,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionInvestigateResponse {
pub session: AiSessionEntry,
pub transcript: Vec<RenderedSessionEvent>,
pub transcript_has_more: bool,
pub correlation: Option<GraphSessionCorrelation>,
pub graph_neighborhood: Option<AppGraphResponse>,
pub skill_events: Vec<SkillEventEntry>,
pub skill_events_truncated: bool,
pub mcp_events: Vec<McpEventEntry>,
pub mcp_events_truncated: bool,
pub hook_events: Vec<HookEventEntry>,
pub hook_events_truncated: bool,
pub artifact_evidence: Vec<ArtifactEvidenceEntry>,
pub artifact_evidence_truncated: bool,
pub observatory: SessionObservatoryEvidence,
pub incident_context: IncidentContextResponse,
pub notifications: Vec<db::notifications::FiringRow>,
pub notifications_truncated: bool,
pub retention_lineage: Vec<SessionRetentionLineageEntry>,
pub retention_lineage_truncated: bool,
pub related_sessions: Vec<AiSessionEntry>,
pub related_sessions_truncated: bool,
pub external_references: Vec<SessionExternalReference>,
pub external_references_truncated: bool,
pub source_counts: BTreeMap<String, usize>,
pub source_evidence: BTreeMap<String, SessionSourceEvidenceSummary>,
pub partial_reasons: Vec<String>,
}

pub fn app_entity_summary(entity: &GraphEntity) -> AppEntitySummary {
AppEntitySummary {
id: entity.id,
Expand Down Expand Up @@ -288,22 +391,22 @@ pub fn app_graph_from_around_response(around: &GraphAroundResponse) -> AppGraphR
}

pub fn safe_passive_text(input: &str, max_chars: usize) -> String {
let mut out = input
static BEARER_VALUE: std::sync::LazyLock<regex::Regex> = std::sync::LazyLock::new(|| {
regex::Regex::new(r"(?i)\bBearer\s+[^\s,;]+").expect("static bearer redaction pattern")
});
let scrubbed = crate::receiver::enrichment::scrub_ai_message(input, None);
let scrubbed = BEARER_VALUE.replace_all(&scrubbed, "[REDACTED]");
let mut out = crate::assessment::redact_secrets(&scrubbed)
.chars()
.filter(|ch| !ch.is_control() || matches!(ch, '\n' | '\t'))
.collect::<String>();
for marker in [
"sk-proj-",
"Bearer ",
"password=",
"token=",
"CORTEX_API_TOKEN=",
] {
out = out.replace(marker, "[redacted]");
}
if out.chars().count() > max_chars {
out = out.chars().take(max_chars).collect::<String>();
out.push_str("...");
}
out
}

#[cfg(test)]
#[path = "investigation_tests.rs"]
mod tests;
25 changes: 25 additions & 0 deletions src/app/models/investigation_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use super::*;

#[test]
fn passive_text_redacts_secret_values_before_truncation() {
for text in [
"password=passive-secret-value",
"Authorization: Bearer passive-secret-value",
"Bearer passive-secret-value",
"CORTEX_API_TOKEN=passive-secret-value",
"token=passive-secret-value",
"sk-proj-passive-secret-value-0123456789abcdefgh",
] {
let output = safe_passive_text(text, 500);
assert!(
!output.contains("passive-secret-value"),
"secret value survived redaction"
);
let short = safe_passive_text(text, 16);
assert!(
!short.contains("passive-secret"),
"truncation exposed a partial secret"
);
}
assert_eq!(safe_passive_text("safe\u{1b} text", 500), "safe text");
}
5 changes: 5 additions & 0 deletions src/app/services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@ mod mcp_backfill;
mod mcp_events;
mod mcp_incidents;
mod rag;
mod session_graph_correlation;
mod session_investigation;
mod session_investigation_budget;
mod session_investigation_sections;
mod session_investigation_support;
mod session_pages;
mod skill_assessment;
mod skill_backfill;
Expand Down
Loading
Loading