-
-
Notifications
You must be signed in to change notification settings - Fork 158
fix(runtime): convert #7811/#7815's new bare raw-handle reads to across_* (#7838) #7840
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| ### Fixed | ||
|
|
||
| - **The raw-handle debt ratchet is green again on `main` (#7838).** It went red at | ||
| 1,013 bare `get_raw_{mut,const}_ptr` reads against a baseline of 998, with four | ||
| per-module violations. The +15 all arrived with the two #6949 rooting fixes in the | ||
| 2026-08-11 batch (#7811, #7815); #7825 — same batch — closed the hole that had been | ||
| letting a PR's own checkout carry the comparison baseline, so the ratchet only | ||
| started seeing them once it landed. While a required gate is red on `main`, no agent | ||
| can tell its own breakage from inherited breakage, which is the condition a real | ||
| regression merges unnoticed in. | ||
|
|
||
| **All fifteen convert to `RuntimeHandle::across_{mut,const}`. No ceiling was raised | ||
| and `raw_handle_debt_baseline.txt` is untouched** — the total lands back on 998 | ||
| exactly, and `regex/replace_fn.rs` returns to its recorded ceiling of 3. | ||
| `disposable.rs`, `messaging.rs` and `builtins/formatting/boxed_primitives.rs` reach | ||
| zero and stay unlisted. | ||
|
|
||
| **`replace_fn.rs` did not need the 3 → 13 raise #7838 proposed.** The proposal rested | ||
| on all thirteen being the one shape `raw_handle_debt_files.txt` sanctions joining the | ||
| list for — a loop whose collection window is a user-visible callback, where a | ||
| `cur_str` helper re-derives at every access and `across_*` (one call ↔ one re-read) | ||
| cannot express it. That describes **three** of them, and those three are | ||
| *pre-existing*: `git show 6af7e5840^:…/replace_fn.rs | grep -c get_raw_` is 3, which | ||
| is what the ceiling of 3 was recorded for. #7811 added **ten**, all of them plain | ||
| root → one allocating `js_string_coerce` → read-for-the-call. Four are two-receiver | ||
| sites; two receivers compose by **nesting** `across_const`, the same way | ||
| `path::value_args::with_two_headers` already does it, and two small private | ||
| combinators carry them. | ||
|
|
||
| - **`SuppressedError` filed its property attributes under a possibly-stale address.** | ||
| `set_nonenum` called `object::set_property_attrs(obj as usize, …)` *after* | ||
| `js_object_set_field_by_name`, which allocates when the object grows. That side table | ||
| is keyed on the address, so a pre-call copy does not fault — it files the attributes | ||
| where nothing will look them up, and `error` / `suppressed` / `message` silently | ||
| become **enumerable** on a `SuppressedError` that grew during the set. | ||
|
|
||
| - **`js_suppressed_error_new` returned a NaN-box built before its last allocating | ||
| call.** `js_nanbox_pointer(obj)` was computed, then the `SuppressedError.prototype` | ||
| lookup ran, then the box was returned. A NaN-box is a frozen address the collector | ||
| cannot rewrite, so the returned value named from-space if that lookup collected. The | ||
| box is now built last. | ||
|
|
||
| Both are the #7192 shape — the store is in-frame but *after* a call that allocates — | ||
| and neither is reachable without evacuation actually moving the receiver, so this is | ||
| ordering hygiene rather than an observed crash. They are recorded because writing the | ||
| `across_*` ordering out is what made them visible, which is the argument for the | ||
| discipline the ratchet enforces. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -613,8 +613,8 @@ pub extern "C" fn js_broadcast_channel_new(name: f64) -> f64 { | |
| // through it. Root it across the coercion and re-read. | ||
| let scope = crate::gc::RuntimeHandleScope::new(); | ||
| let obj_handle = scope.root_raw_mut_ptr(obj); | ||
| let name_ptr = crate::builtins::js_string_coerce(name); | ||
| let obj = obj_handle.get_raw_mut_ptr::<object::ObjectHeader>(); | ||
| let (name_ptr, obj) = obj_handle | ||
| .across_mut::<object::ObjectHeader, _>(|| crate::builtins::js_string_coerce(name)); | ||
|
Comment on lines
+616
to
+617
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 8 \
'across_mut|obj_handle|get_raw_mut_ptr|set_field|install_method|boxed_object' \
crates/perry-runtime/src/messaging.rsRepository: PerryTS/perry Length of output: 9067 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- RuntimeHandleScope and across_mut definitions ---'
rg -n -C 12 \
'struct RuntimeHandleScope|impl RuntimeHandleScope|fn root_raw_mut_ptr|fn across_mut|get_raw_mut_ptr' \
crates/perry-runtime crates
printf '%s\n' '--- Relevant object mutation APIs ---'
rg -n -C 10 \
'fn js_object_set_field_by_name|js_object_define_accessor|fn set_object_prototype|fn js_object_alloc' \
crates/perry-runtime
printf '%s\n' '--- GC/rooting APIs and mutable-root scanner registration ---'
rg -n -C 8 \
'gc_register_mutable_root_scanner|root_raw_mut_ptr|root_nanbox_f64|across_mut' \
crates/perry-runtime/srcRepository: PerryTS/perry Length of output: 50370 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- Definition files ---'
rg -l \
'struct RuntimeHandleScope|pub struct RuntimeHandleScope|fn js_object_set_field_by_name|pub fn js_object_set_field_by_name' \
crates/perry-runtime/src crates/perry-runtime/tests 2>/dev/null | sort -u
printf '%s\n' '--- Focused handle API matches ---'
rg -n \
'struct RuntimeHandleScope|impl RuntimeHandleScope|root_raw_mut_ptr|across_mut|get_raw_mut_ptr' \
crates/perry-runtime/src/gc crates/perry-runtime/src --glob '*.rs' \
| grep -E '(^|/)(handles?|mod|runtime|scope|gc)(\.rs:|/)' | head -n 240
printf '%s\n' '--- Focused object API matches ---'
rg -n \
'fn js_object_set_field_by_name|fn js_object_define_accessor|fn set_object_prototype|fn js_object_alloc' \
crates/perry-runtime/src --glob '*.rs' | head -n 120Repository: PerryTS/perry Length of output: 413 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- Runtime handle implementation ---'
sed -n '1,280p' crates/perry-runtime/src/gc/roots/runtime_handles.rs
printf '%s\n' '--- Field setter implementation ---'
sed -n '1,260p' crates/perry-runtime/src/object/field_set_by_name.rs
printf '%s\n' '--- BroadcastChannel call site and nearby helpers ---'
sed -n '598,632p' crates/perry-runtime/src/messaging.rs
printf '%s\n' '--- Allocation/coercion definitions ---'
rg -n -C 8 \
'pub extern "C" fn js_string_coerce|fn js_string_coerce|pub fn js_string_coerce|pub extern "C" fn js_object_alloc|fn js_object_alloc|pub fn js_object_alloc' \
crates/perry-runtime/src --glob '*.rs'Repository: PerryTS/perry Length of output: 40820 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- Messaging helper definitions ---'
sed -n '1,135p' crates/perry-runtime/src/messaging.rs
printf '%s\n' '--- String coercion body ---'
sed -n '625,705p' crates/perry-runtime/src/builtins/numbers.rs
printf '%s\n' '--- Object allocation body ---'
sed -n '119,163p' crates/perry-runtime/src/object/alloc.rs
printf '%s\n' '--- Setter tail and handle-related logic ---'
rg -n -C 12 \
'RuntimeHandleScope|root_|js_object_set_field_by_name\(|arena_alloc|key\(' \
crates/perry-runtime/src/object/field_set_by_name crates/perry-runtime/src/object/field_set_by_name.rs \
| head -n 360
printf '%s\n' '--- Closure and string allocation helpers ---'
rg -n -C 10 \
'fn closure_value|fn js_string_from_bytes|pub.*js_string_from_bytes|js_closure_alloc' \
crates/perry-runtime/src/messaging.rs crates/perry-runtime/src --glob '*.rs' \
| head -n 300Repository: PerryTS/perry Length of output: 50369 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- String allocation implementation ---'
rg -n -C 18 \
'pub extern "C" fn js_string_from_bytes|pub fn js_string_from_bytes|fn js_string_from_bytes' \
crates/perry-runtime/src/string crates/perry-runtime/src --glob '*.rs' | head -n 180
printf '%s\n' '--- Closure allocation implementation ---'
rg -n -C 20 \
'pub extern "C" fn js_closure_alloc|pub fn js_closure_alloc|fn js_closure_alloc' \
crates/perry-runtime/src/closure crates/perry-runtime/src --glob '*.rs' | head -n 180
printf '%s\n' '--- Root refresh macro and allocation points in setter tail ---'
rg -n -C 10 \
'macro_rules! refresh_roots_after_alloc|refresh_roots_after_alloc!|js_string_intern|js_array_push|arena_alloc_gc' \
crates/perry-runtime/src/object/field_set_by_name/tail.rs | head -n 320
printf '%s\n' '--- Existing messaging GC-rooting tests or references ---'
rg -n -C 12 \
'broadcast_channel|js_broadcast_channel_new|6949|root_nanbox|root_string_ptr' \
crates/perry-runtime/src/gc crates/perry-runtime/src/messaging.rs --glob '*.rs' | head -n 260Repository: PerryTS/perry Length of output: 50369 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
messaging = Path("crates/perry-runtime/src/messaging.rs").read_text()
coerce = Path("crates/perry-runtime/src/builtins/numbers.rs").read_text()
string_alloc = Path("crates/perry-runtime/src/string/alloc.rs").read_text()
string_mod = Path("crates/perry-runtime/src/string/mod.rs").read_text()
tail = Path("crates/perry-runtime/src/object/field_set_by_name/tail.rs").read_text()
checks = {
"scope starts after constructor field write":
messaging.index('set_field(\n obj,\n "constructor",\n get_global_constructor("BroadcastChannel"),\n );')
< messaging.index('let scope = crate::gc::RuntimeHandleScope::new();', messaging.index('js_broadcast_channel_new')),
"set_field allocates its key before calling setter":
'object::js_object_set_field_by_name(obj, key(name), value);' in messaging,
"ordinary string allocation reaches storage allocator":
'js_string_from_bytes_with_capacity(data, len, len)' in string_alloc
and 'let (ptr, data_ptr) = string_storage_alloc(capacity);' in string_alloc,
"non-string coercion returns a newly allocated string":
'js_string_from_bytes(result.as_ptr(), result.len() as u32)' in coerce,
"setter roots only after entry":
'let obj_handle = scope.root_raw_mut_ptr(obj);' in tail
and 'let key_handle = scope.root_string_ptr(key);' in tail,
"setter refreshes its own local object after internal allocations":
'obj = obj_handle.get_raw_mut_ptr::<ObjectHeader>();' in tail,
"messaging keeps coerced name as an unrooted raw-pointer NaN-box":
'let name_value = f64::from_bits(JSValue::string_ptr(name_ptr).bits());' in messaging
and 'root_string_ptr(name_ptr)' not in messaging
and 'root_nanbox_f64(name_value)' not in messaging,
}
for label, result in checks.items():
print(f"{'PASS' if result else 'FAIL'}: {label}")
assert all(checks.values())
PY
printf '%s\n' '--- Storage allocator and exact coercion allocation path ---'
rg -n -C 16 \
'fn string_storage_alloc|pub.*string_storage_alloc|js_string_from_bytes_with_capacity' \
crates/perry-runtime/src/string --glob '*.rs' | head -n 180Repository: PerryTS/perry Length of output: 16489 Root Create 🤖 Prompt for AI AgentsSource: Learnings |
||
| let name_value = f64::from_bits(JSValue::string_ptr(name_ptr).bits()); | ||
| set_field(obj, "name", name_value); | ||
|
Comment on lines
+616
to
619
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 8 \
'name_ptr|name_value|root_nanbox_f64|set_field|js_object_set_field_by_name' \
crates/perry-runtime/src/messaging.rs crates/perry-runtime/src/atomics.rsRepository: PerryTS/perry Length of output: 19219 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- messaging helpers and target ---'
sed -n '1,80p;598,628p' crates/perry-runtime/src/messaging.rs
printf '%s\n' '--- handle and GC APIs ---'
rg -n -C 12 \
'struct RuntimeHandleScope|fn root_nanbox_f64|fn root_raw_mut_ptr|fn across_mut|struct .*Handle|root_string_ptr' \
crates/perry-runtime/src
printf '%s\n' '--- coercion and field-write implementations ---'
rg -n -C 16 \
'fn js_string_coerce|js_string_coerce|js_object_set_field_by_name|fn key\\(' \
crates/perry-runtime/srcRepository: PerryTS/perry Length of output: 50370 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- messaging helpers and target ---'
sed -n '1,80p;598,628p' crates/perry-runtime/src/messaging.rs
printf '%s\n' '--- handle and GC APIs ---'
rg -n -C 12 \
'struct RuntimeHandleScope|fn root_nanbox_f64|fn root_raw_mut_ptr|fn across_mut|struct .*Handle|root_string_ptr' \
crates/perry-runtime/src
printf '%s\n' '--- coercion and field-write implementations ---'
rg -n -C 16 \
'fn js_string_coerce|js_string_coerce|js_object_set_field_by_name|fn key\(' \
crates/perry-runtime/srcRepository: PerryTS/perry Length of output: 50370 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- candidate definitions ---'
rg -l \
'pub struct RuntimeHandleScope|impl RuntimeHandleScope|fn js_string_coerce|pub extern "C" fn js_object_set_field_by_name|pub fn js_object_set_field_by_name' \
crates/perry-runtime/src | sort
printf '%s\n' '--- exact target references ---'
rg -n -C 6 \
'pub struct RuntimeHandleScope|root_nanbox_f64|root_raw_mut_ptr|across_mut' \
crates/perry-runtime/src/gc.rs crates/perry-runtime/src/gc 2>/dev/null || true
printf '%s\n' '--- exact coercion references ---'
rg -n -C 10 \
'fn js_string_coerce|pub extern "C" fn js_string_coerce|js_string_coerce' \
crates/perry-runtime/src/builtins.rs crates/perry-runtime/src/builtins 2>/dev/null | head -n 180
printf '%s\n' '--- exact field setter references ---'
rg -n -C 12 \
'js_object_set_field_by_name' \
crates/perry-runtime/src/object.rs crates/perry-runtime/src/object 2>/dev/null | head -n 240Repository: PerryTS/perry Length of output: 50371 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- runtime handle implementation ---'
sed -n '1,115p;180,245p' crates/perry-runtime/src/gc/roots/runtime_handles.rs
printf '%s\n' '--- string coercion implementation ---'
rg -n 'js_string_coerce' crates/perry-runtime/src/builtins/numbers.rs
line="$(rg -n -m1 'js_string_coerce' crates/perry-runtime/src/builtins/numbers.rs | cut -d: -f1)"
start=$((line > 25 ? line - 25 : 1))
end=$((line + 65))
sed -n "${start},${end}p" crates/perry-runtime/src/builtins/numbers.rs
printf '%s\n' '--- field setter implementation ---'
rg -n 'js_object_set_field_by_name' crates/perry-runtime/src/object/field_set_by_name.rs
line="$(rg -n -m1 'js_object_set_field_by_name' crates/perry-runtime/src/object/field_set_by_name.rs | cut -d: -f1)"
start=$((line > 25 ? line - 25 : 1))
end=$((line + 180))
sed -n "${start},${end}p" crates/perry-runtime/src/object/field_set_by_name.rsRepository: PerryTS/perry Length of output: 21376 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- messaging helper ---'
sed -n '55,72p;604,626p' crates/perry-runtime/src/messaging.rs
printf '%s\n' '--- key-string allocation path ---'
rg -n -m 5 'pub extern "C" fn js_string_from_bytes|pub fn js_string_from_bytes|fn js_string_from_bytes' \
crates/perry-runtime/src
file="$(rg -l -m 1 'pub extern "C" fn js_string_from_bytes|pub fn js_string_from_bytes|fn js_string_from_bytes' \
crates/perry-runtime/src | head -n 1)"
line="$(rg -n -m 1 'pub extern "C" fn js_string_from_bytes|pub fn js_string_from_bytes|fn js_string_from_bytes' "$file" | cut -d: -f1)"
start=$((line > 15 ? line - 15 : 1))
end=$((line + 90))
sed -n "${start},${end}p" "$file"
printf '%s\n' '--- read-only structural verifier ---'
python3 - <<'PY'
from pathlib import Path
p = Path("crates/perry-runtime/src/messaging.rs")
s = p.read_text()
helper = s[s.index("fn set_field("):s.index("fn get_global_constructor(")]
target = s[s.index("pub extern \"C\" fn js_broadcast_channel_new"):s.index("pub(crate) extern \"C\" fn js_broadcast_channel_constructor_call_error")]
checks = {
"set_field passes raw obj before constructing key": "js_object_set_field_by_name(obj, key(name), value)" in helper,
"key constructs a runtime string": "js_string_from_bytes(name.as_ptr(), name.len() as u32)" in s[s.index("fn key("):s.index("fn set_field(")],
"target roots object across coercion": "let obj_handle = scope.root_raw_mut_ptr(obj);" in target,
"target does not root coerced name value": "root_nanbox_f64(name_value)" not in target,
"target uses raw name value for field write": 'set_field(obj, "name", name_value);' in target,
}
for name, ok in checks.items():
print(f"{name}: {'yes' if ok else 'no'}")
raise SystemExit(0 if all(checks.values()) else 1)
PYRepository: PerryTS/perry Length of output: 8005 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- js_string_from_bytes allocator ---'
sed -n '1,95p' crates/perry-runtime/src/string/alloc.rs
printf '%s\n' '--- setter rooting and allocation-sensitive sections ---'
rg -n 'RuntimeHandleScope|root_raw|root_nanbox|js_string_from_bytes|js_object_alloc|gc_malloc|arena_alloc|set_object_keys_array|transition_cache_lookup' \
crates/perry-runtime/src/object/field_set_by_name.rs \
crates/perry-runtime/src/object/field_set_by_name/tail.rs \
crates/perry-runtime/src/object/field_set_by_name/write_helpers.rs
printf '%s\n' '--- setter tail start ---'
sed -n '1,220p' crates/perry-runtime/src/object/field_set_by_name/tail.rsRepository: PerryTS/perry Length of output: 19029 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- install_method and closure_value ---'
rg -n -C 18 \
'fn closure_value|fn install_method|closure_value\\(' \
crates/perry-runtime/src/messaging.rs
printf '%s\n' '--- complete string allocation helper ---'
rg -n -m 3 'js_string_from_bytes_with_capacity' crates/perry-runtime/src/string/alloc.rs
line="$(rg -n -m1 'pub extern "C" fn js_string_from_bytes_with_capacity|fn js_string_from_bytes_with_capacity' crates/perry-runtime/src/string/alloc.rs | cut -d: -f1)"
start=$((line > 5 ? line - 5 : 1))
end=$((line + 100))
sed -n "${start},${end}p" crates/perry-runtime/src/string/alloc.rsRepository: PerryTS/perry Length of output: 303 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- install_method and closure_value ---'
rg -n -C 18 -e 'fn closure_value' -e 'fn install_method' -e 'closure_value\(' \
crates/perry-runtime/src/messaging.rs
printf '%s\n' '--- complete string allocation helper ---'
rg -n -m 3 'js_string_from_bytes_with_capacity' crates/perry-runtime/src/string/alloc.rs
line="$(rg -n -m1 'pub extern "C" fn js_string_from_bytes_with_capacity|fn js_string_from_bytes_with_capacity' crates/perry-runtime/src/string/alloc.rs | cut -d: -f1)"
start=$((line > 5 ? line - 5 : 1))
end=$((line + 100))
sed -n "${start},${end}p" crates/perry-runtime/src/string/alloc.rsRepository: PerryTS/perry Length of output: 8138 Keep the coerced value and receiver rooted across helper allocations.
🤖 Prompt for AI AgentsSource: Learnings |
||
| install_method(obj, "close", noop0 as *const u8, 0); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 20264
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 50370
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 1796
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 32578
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 1257
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 40579
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 1621
Preserve handle-based roots across every allocation.
across_mutreturns one post-collection address. Later allocations can evacuate that object and the returned string pointer.boxed_primitives.rs, root and reload both the object and string handles. Apply this insideinstall_string_wrapper_indicesandinstall_string_wrapper_length.messaging.rs, rootname_ptr. Root and reload the object insideset_fieldacrosskey(name)and insideinstall_methodacrossclosure_value.📍 Affects 2 files
crates/perry-runtime/src/builtins/formatting/boxed_primitives.rs#L311-L324(this comment)crates/perry-runtime/src/messaging.rs#L616-L617🤖 Prompt for AI Agents
Source: Learnings
🩺 Stability & Availability | 🔴 Critical | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 6258
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 50370
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 11912
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 50371
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 50371
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 50370
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 49006
🏁 Script executed:
Repository: PerryTS/perry
Length of output: 459
Use refreshed GC handles throughout boxed String installation.
obj,ptr, andchare raw pointers held across allocations.js_string_coerce,js_string_char_at,js_string_from_bytes, and key-array growth can evacuate them. Root the coerced string, keep the object handle active, reload both pointers after each allocation, and root each intermediate character string until its field store completes. Do not pass raw pointer snapshots through the helpers.🤖 Prompt for AI Agents
Source: Learnings