@@ -253,6 +253,7 @@ impl Database {
253253 verified : false ,
254254 source : format ! ( "connector:{}" , name) ,
255255 workspace_hash : String :: new ( ) ,
256+ agent_id : String :: new ( ) ,
256257 created_at_unix_ms : now,
257258 last_accessed_unix_ms : now,
258259 embedding : None ,
@@ -571,7 +572,7 @@ impl Database {
571572 decay_score, retrieval_count, layer, topic_path,
572573 archived, archive_reason, links, verified, source,
573574 created_at_unix_ms, last_accessed_unix_ms, embedding,
574- always_on, certainty, workspace_hash
575+ always_on, certainty, workspace_hash, agent_id
575576 FROM entities WHERE archived = 0 AND embedding IS NOT NULL LIMIT {}" ,
576577 max_scan
577578 ) ) ?;
@@ -854,9 +855,9 @@ impl Database {
854855 decay_score = ?5, layer = ?6, topic_path = ?7,
855856 archived = ?8, archive_reason = ?9, links = ?10,
856857 verified = ?11, source = ?12, last_accessed_unix_ms = ?13,
857- always_on = ?14, certainty = ?15, workspace_hash = ?16,
858+ always_on = ?14, certainty = ?15, workspace_hash = ?16, agent_id = ?17,
858859 retrieval_count = retrieval_count + 1
859- WHERE id = ?17 " ,
860+ WHERE id = ?18 " ,
860861 params ! [
861862 body_encrypted,
862863 entity. status,
@@ -874,6 +875,7 @@ impl Database {
874875 entity. always_on as i32 ,
875876 entity. certainty,
876877 entity. workspace_hash,
878+ entity. agent_id,
877879 id,
878880 ] ,
879881 ) ?;
@@ -914,11 +916,11 @@ impl Database {
914916 decay_score, retrieval_count, layer, topic_path,
915917 archived, archive_reason, links, verified, source,
916918 always_on, certainty, created_at_unix_ms, last_accessed_unix_ms,
917- workspace_hash)
919+ workspace_hash, agent_id )
918920 VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7,
919921 ?8, ?9, ?10, ?11,
920922 ?12, ?13, ?14, ?15, ?16,
921- ?17, ?18, ?19, ?20, ?21)" ,
923+ ?17, ?18, ?19, ?20, ?21, ?22 )" ,
922924 params ! [
923925 id,
924926 entity. category,
@@ -941,6 +943,7 @@ impl Database {
941943 entity. created_at_unix_ms,
942944 entity. last_accessed_unix_ms,
943945 entity. workspace_hash,
946+ entity. agent_id,
944947 ] ,
945948 ) ?;
946949
@@ -1104,6 +1107,13 @@ impl Database {
11041107 param_values. push ( Box :: new ( ws. clone ( ) ) ) ;
11051108 }
11061109
1110+ // Filter by agent_id (v1.2.0 attribution). When set, only entities
1111+ // written by the specified agent are visible.
1112+ if let Some ( ref aid) = params. agent_id {
1113+ conditions. push ( format ! ( "agent_id = ?{}" , param_values. len( ) + 1 ) ) ;
1114+ param_values. push ( Box :: new ( aid. clone ( ) ) ) ;
1115+ }
1116+
11071117 // Exclude archived unless explicitly requested
11081118 if !params. include_archived {
11091119 conditions. push ( "archived = 0" . to_string ( ) ) ;
@@ -1115,7 +1125,7 @@ impl Database {
11151125 decay_score, retrieval_count, layer, topic_path,
11161126 archived, archive_reason, links, verified, source,
11171127 created_at_unix_ms, last_accessed_unix_ms, embedding,
1118- always_on, certainty, workspace_hash
1128+ always_on, certainty, workspace_hash, agent_id
11191129 FROM entities" ,
11201130 ) ;
11211131
@@ -1284,7 +1294,7 @@ impl Database {
12841294 decay_score, retrieval_count, layer, topic_path,
12851295 archived, archive_reason, links, verified, source,
12861296 created_at_unix_ms, last_accessed_unix_ms, embedding,
1287- always_on, certainty, workspace_hash
1297+ always_on, certainty, workspace_hash, agent_id
12881298 FROM entities WHERE category = ?1 AND key = ?2 LIMIT 1" ,
12891299 ) ?;
12901300
@@ -1415,8 +1425,8 @@ impl Database {
14151425 self . conn . execute (
14161426 "INSERT INTO journal
14171427 (id, event_type, evaluated_json, acted_json, forward_json,
1418- category, key, entity_id, created_at_unix_ms)
1419- VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)" ,
1428+ category, key, entity_id, agent_id, created_at_unix_ms)
1429+ VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10 )" ,
14201430 params ! [
14211431 event. id,
14221432 event. event_type,
@@ -1426,6 +1436,7 @@ impl Database {
14261436 event. category,
14271437 event. key,
14281438 event. entity_id,
1439+ event. agent_id,
14291440 event. created_at_unix_ms,
14301441 ] ,
14311442 ) ?;
@@ -1473,7 +1484,7 @@ impl Database {
14731484
14741485 let mut sql = String :: from (
14751486 "SELECT id, event_type, evaluated_json, acted_json, forward_json,
1476- category, key, entity_id, created_at_unix_ms
1487+ category, key, entity_id, agent_id, created_at_unix_ms
14771488 FROM journal" ,
14781489 ) ;
14791490
@@ -1508,7 +1519,8 @@ impl Database {
15081519 category : row. get ( 5 ) ?,
15091520 key : row. get ( 6 ) ?,
15101521 entity_id : row. get ( 7 ) ?,
1511- created_at_unix_ms : row. get ( 8 ) ?,
1522+ agent_id : row. get :: < _ , Option < String > > ( 8 ) . unwrap_or ( None ) . unwrap_or_default ( ) ,
1523+ created_at_unix_ms : row. get ( 9 ) ?,
15121524 } )
15131525 } ) ?;
15141526
@@ -1777,7 +1789,7 @@ impl Database {
17771789 decay_score, retrieval_count, layer, topic_path,
17781790 archived, archive_reason, links, verified, source,
17791791 created_at_unix_ms, last_accessed_unix_ms, embedding,
1780- always_on, certainty, workspace_hash
1792+ always_on, certainty, workspace_hash, agent_id
17811793 FROM entities WHERE id = ?1" ,
17821794 ) ?;
17831795 let mut rows = stmt. query_map ( params ! [ id] , |row| {
@@ -1811,7 +1823,7 @@ impl Database {
18111823 decay_score, retrieval_count, layer, topic_path,
18121824 archived, archive_reason, links, verified, source,
18131825 created_at_unix_ms, last_accessed_unix_ms, embedding,
1814- always_on, certainty, workspace_hash
1826+ always_on, certainty, workspace_hash, agent_id
18151827 FROM entities WHERE archived = 0" ,
18161828 ) ;
18171829 let mut params: Vec < Box < dyn rusqlite:: types:: ToSql > > = Vec :: new ( ) ;
@@ -1858,7 +1870,7 @@ impl Database {
18581870 ) -> Result < Vec < JournalEvent > , Box < dyn std:: error:: Error > > {
18591871 let mut stmt = self . conn . prepare (
18601872 "SELECT id, event_type, evaluated_json, acted_json, forward_json,
1861- category, key, entity_id, created_at_unix_ms
1873+ category, key, entity_id, agent_id, created_at_unix_ms
18621874 FROM journal ORDER BY created_at_unix_ms DESC LIMIT ?1" ,
18631875 ) ?;
18641876 let rows = stmt. query_map ( params ! [ limit] , |row| {
@@ -1871,7 +1883,8 @@ impl Database {
18711883 category : row. get :: < _ , String > ( 5 ) . unwrap_or_default ( ) ,
18721884 key : row. get :: < _ , String > ( 6 ) . unwrap_or_default ( ) ,
18731885 entity_id : row. get :: < _ , String > ( 7 ) . unwrap_or_default ( ) ,
1874- created_at_unix_ms : row. get ( 8 ) ?,
1886+ agent_id : row. get :: < _ , Option < String > > ( 8 ) . unwrap_or ( None ) . unwrap_or_default ( ) ,
1887+ created_at_unix_ms : row. get ( 9 ) ?,
18751888 } )
18761889 } ) ?;
18771890 let mut items = Vec :: new ( ) ;
@@ -2326,6 +2339,7 @@ last_accessed: {}
23262339 always_on : false ,
23272340 certainty : 0.5 ,
23282341 workspace_hash : String :: new ( ) ,
2342+ agent_id : String :: new ( ) ,
23292343 created_at_unix_ms : now_ms ( ) ,
23302344 last_accessed_unix_ms : now_ms ( ) ,
23312345 embedding : None ,
@@ -2478,7 +2492,7 @@ last_accessed: {}
24782492 decay_score, retrieval_count, layer, topic_path,
24792493 archived, archive_reason, links, verified, source,
24802494 created_at_unix_ms, last_accessed_unix_ms, embedding,
2481- always_on, certainty, workspace_hash
2495+ always_on, certainty, workspace_hash, agent_id
24822496 FROM entities
24832497 WHERE archived = 0 AND ({})
24842498 ORDER BY decay_score DESC, retrieval_count DESC
@@ -2745,6 +2759,7 @@ fn entity_from_row(
27452759 always_on : row. get :: < _ , i32 > ( 19 ) . unwrap_or ( 0 ) != 0 ,
27462760 certainty : row. get :: < _ , f64 > ( 20 ) . unwrap_or ( 0.5 ) ,
27472761 workspace_hash : row. get :: < _ , Option < String > > ( 21 ) . unwrap_or ( None ) . unwrap_or_default ( ) ,
2762+ agent_id : row. get :: < _ , Option < String > > ( 22 ) . unwrap_or ( None ) . unwrap_or_default ( ) ,
27482763 created_at_unix_ms : row. get ( 16 ) ?,
27492764 last_accessed_unix_ms : row. get ( 17 ) ?,
27502765 embedding : None ,
@@ -2785,6 +2800,7 @@ mod tests {
27852800 always_on : false ,
27862801 certainty : 0.5 ,
27872802 workspace_hash : String :: new ( ) ,
2803+ agent_id : String :: new ( ) ,
27882804 created_at_unix_ms : now_ms ( ) ,
27892805 last_accessed_unix_ms : now_ms ( ) ,
27902806 embedding : None ,
@@ -3007,6 +3023,7 @@ mod tests {
30073023 category : "decision" . to_string ( ) ,
30083024 key : "use-pg" . to_string ( ) ,
30093025 entity_id : "e1" . to_string ( ) ,
3026+ agent_id : "agent-1" . to_string ( ) ,
30103027 created_at_unix_ms : now_ms ( ) ,
30113028 } ;
30123029 db. journal ( & event) . unwrap ( ) ;
@@ -3325,6 +3342,7 @@ mod tests {
33253342 diversity_halving : 1.0 ,
33263343 diversity_per_query_share : 0.0 ,
33273344 workspace_hash : None ,
3345+ agent_id : None ,
33283346 } ,
33293347 )
33303348 . unwrap ( ) ;
@@ -3451,6 +3469,7 @@ mod tests {
34513469 diversity_halving : 1.0f64 ,
34523470 diversity_per_query_share : 0.0f64 ,
34533471 workspace_hash : None ,
3472+ agent_id : None ,
34543473 } ) {
34553474 Ok ( _) => { } ,
34563475 Err ( e) => {
@@ -3509,6 +3528,7 @@ mod tests {
35093528 diversity_halving : 1.0 ,
35103529 diversity_per_query_share : 0.0 ,
35113530 workspace_hash : ws,
3531+ agent_id : None ,
35123532 } ;
35133533
35143534 // Scope to "alpha" — should only see ent_a
@@ -3551,6 +3571,7 @@ mod tests {
35513571 mode : crate :: models:: SearchMode :: Fts5 , embedding : None , preview_cap : None ,
35523572 always_on : None , content_weight : 0.0 , diversity_halving : 1.0 ,
35533573 diversity_per_query_share : 0.0 , workspace_hash : None ,
3574+ agent_id : None ,
35543575 } ;
35553576 let results = db. recall ( & params) . unwrap ( ) ;
35563577 let found = results. iter ( ) . find ( |e| e. key == "key1" ) . expect ( "entity recalled" ) ;
@@ -3559,4 +3580,78 @@ mod tests {
35593580 assert_eq ! ( found. certainty, 0.9 , "certainty must roundtrip" ) ;
35603581 }
35613582
3583+
3584+ #[ test]
3585+ fn agent_id_filters_in_recall ( ) {
3586+ // Phase 2 Week 4-6: entities tagged with agent_id are filterable.
3587+ let ( db, _path) = temp_db ( ) ;
3588+
3589+ let mut ent_a = make_entity ( "aid-a" , "agents" , "agent-a-key" , r#"{"content":"alpha agent xyzzy unique data"}"# ) ;
3590+ ent_a. agent_id = "squad-leader" . to_string ( ) ;
3591+ db. remember ( & ent_a) . unwrap ( ) ;
3592+
3593+ let mut ent_b = make_entity ( "aid-b" , "agents" , "agent-b-key" , r#"{"content":"beta agent plugh distinct info"}"# ) ;
3594+ ent_b. agent_id = "scout" . to_string ( ) ;
3595+ db. remember ( & ent_b) . unwrap ( ) ;
3596+
3597+ // No filter — sees both
3598+ let all = db. recall ( & crate :: models:: RecallParams {
3599+ query : "agent" . to_string ( ) , agent_id : None ,
3600+ ..crate :: models:: RecallParams :: default ( )
3601+ } ) . unwrap ( ) ;
3602+ let all_keys: Vec < _ > = all. iter ( ) . map ( |e| e. key . as_str ( ) ) . collect ( ) ;
3603+ assert ! ( all_keys. contains( & "agent-a-key" ) ) ;
3604+ assert ! ( all_keys. contains( & "agent-b-key" ) ) ;
3605+
3606+ // Filter by "squad-leader" — only sees ent_a
3607+ let squad = db. recall ( & crate :: models:: RecallParams {
3608+ query : "agent" . to_string ( ) , agent_id : Some ( "squad-leader" . to_string ( ) ) ,
3609+ ..crate :: models:: RecallParams :: default ( )
3610+ } ) . unwrap ( ) ;
3611+ let squad_keys: Vec < _ > = squad. iter ( ) . map ( |e| e. key . as_str ( ) ) . collect ( ) ;
3612+ assert ! ( squad_keys. contains( & "agent-a-key" ) ) ;
3613+ assert ! ( !squad_keys. contains( & "agent-b-key" ) ) ;
3614+ assert_eq ! ( squad. len( ) , 1 ) ;
3615+ }
3616+
3617+ #[ test]
3618+ fn agent_id_roundtrips ( ) {
3619+ let ( db, _path) = temp_db ( ) ;
3620+ let mut ent = make_entity ( "rt-aid" , "agents" , "k" , r#"{"content":"roundtrip"}"# ) ;
3621+ ent. workspace_hash = "scope" . to_string ( ) ;
3622+ ent. agent_id = "secret-agent-man" . to_string ( ) ;
3623+ db. remember ( & ent) . unwrap ( ) ;
3624+
3625+ let results = db. recall ( & crate :: models:: RecallParams {
3626+ query : "roundtrip" . to_string ( ) ,
3627+ ..crate :: models:: RecallParams :: default ( )
3628+ } ) . unwrap ( ) ;
3629+ let found = results. iter ( ) . find ( |e| e. key == "k" ) . unwrap ( ) ;
3630+ assert_eq ! ( found. agent_id, "secret-agent-man" ) ;
3631+ assert_eq ! ( found. workspace_hash, "scope" ) ;
3632+ }
3633+
3634+ #[ test]
3635+ fn journal_agent_attribution ( ) {
3636+ let ( db, _path) = temp_db ( ) ;
3637+ let event = crate :: models:: JournalEvent {
3638+ id : "jrn-agent-1" . to_string ( ) ,
3639+ event_type : "test" . to_string ( ) ,
3640+ evaluated_json : "{}" . to_string ( ) ,
3641+ acted_json : "{}" . to_string ( ) ,
3642+ forward_json : "{}" . to_string ( ) ,
3643+ category : "test" . to_string ( ) ,
3644+ key : "t1" . to_string ( ) ,
3645+ entity_id : String :: new ( ) ,
3646+ agent_id : "security-bot" . to_string ( ) ,
3647+ created_at_unix_ms : now_ms ( ) ,
3648+ } ;
3649+ db. journal ( & event) . unwrap ( ) ;
3650+
3651+ let events = db. timeline ( & crate :: models:: TimelineParams :: default ( ) ) . unwrap ( ) ;
3652+ assert ! ( !events. is_empty( ) ) ;
3653+ let found = events. iter ( ) . find ( |e| e. id == "jrn-agent-1" ) . unwrap ( ) ;
3654+ assert_eq ! ( found. agent_id, "security-bot" ) ;
3655+ }
3656+
35623657}
0 commit comments