# Claude Session State Dump
_Generated: 2026-03-24_

## Active Repos
- `/home/merzky/radical/radical.edge.2` (branch: `app/xgfabric`)
- `/home/merzky/radical/rose` (no branch noted)

## Workflow Rule
**Always plan first, wait for literal "go" before implementing anything.**

---

## Completed This Session

### 1. `-p`/`--plugins` CLI argument for edge service
All code is in place and 240 tests pass.

**`src/radical/edge/service.py`** — new top-level function + updated `__init__` + updated `_load_plugins`:
```python
def _resolve_plugin_names(requested: list, available: list) -> list:
    if requested == ['all']:
        return list(available)
    result = []
    for token in requested:
        if token in available:
            result.append(token)
            continue
        matches = [p for p in available if p.startswith(token)]
        if not matches:
            raise ValueError(
                f"No plugin matches '{token}'. "
                f"Available: {', '.join(sorted(available))}"
            )
        if len(matches) > 1:
            raise ValueError(
                f"Ambiguous plugin name '{token}': matches {sorted(matches)}"
            )
        result.append(matches[0])
    return result
```

`EdgeService.__init__` now accepts `plugins: Optional[list] = None` and stores `self._plugin_filter: list = plugins or ['all']`.

`_load_plugins` resolves via:
```python
to_load = _resolve_plugin_names(self._plugin_filter, Plugin.get_plugin_names())
```

**`bin/radical-edge-service.py`**:
```python
parser.add_argument("--plugins", "-p", default="all",
                    help="Comma-separated plugins to load (default: all). "
                         "Prefix matching supported: 'sys'→sysinfo, "
                         "'q'→queue_info, 'ro'→rose, etc.")
plugins = [t.strip() for t in args.plugins.split(',') if t.strip()]
service = EdgeService(bridge_url=edge_url, name=edge_name, plugins=plugins)
```

### 2. ROSE task notification UI fixes (in `rose/rose/service/api/rose.js`)
- "Waiting…" placeholder now cleared on first workflow_state update (was persisting)
- `metric=null` display fixed: `stats.metric_value != null` instead of `!== undefined`
- `task_event` handling: appends colored `[task.N] ✓/✗ excerpt` lines below state info
- State info written into stable `.rose-state-info` child div so task lines survive re-renders

### 3. Explorer HTML fix (`src/radical/edge/data/edge_explorer.html`)
`pendingNotifications` only stores notifications matching `notificationConfig.topic`:
```javascript
if (taskId && (!configTopic || data.topic === configTopic)) {
    pendingNotifications[`${edgeName}/${plugin}/${taskId}`] = data;
}
```
This prevents `task_event` from overwriting `workflow_state` in the replay dict.

---

## Deferred / Not Working

### Task event notifications (`task_event`) never reach browser
Full debugging notes in `./claude_notifications.md`.

**What's implemented** (in `rose/rose/service/api/rest.py`, `_run_workflow`):
```python
task_count     = 0
_orig_register = learner._register_task

def _tracked_register(task_obj, deps=None):
    nonlocal task_count
    task_count += 1
    tid    = task_count
    future = _orig_register(task_obj, deps)

    def _on_done(fut):
        if fut.cancelled():
            return
        exc     = fut.exception()
        is_ok   = exc is None
        raw     = str(fut.result() or "") if is_ok else str(exc)
        excerpt = next((l.strip() for l in raw.splitlines() if l.strip()), "")[:120]
        if self._notify:
            self._notify("task_event", {"wf_id": wf_id, "task_id": tid,
                                        "ok": is_ok, "excerpt": excerpt})

    future.add_done_callback(_on_done)
    return future

learner._register_task = _tracked_register
```
This code is **live and harmless** — no diagnostic logs left in.

**What's confirmed broken**: `task_event` topic never appears in browser console. `workflow_state` works fine.

**Most likely root cause (not yet fixed)**:
`ParallelActiveLearner` bypass — the wrapping is set on the top-level learner, but
`ParallelActiveLearner.start()` creates internal `SequentialActiveLearner` instances that
call their own `_register_task`, not the wrapped one.

**Planned fix** (to try next session — wait for "go"):
```python
# Add after: learner._register_task = _tracked_register
if hasattr(learner, '_create_sequential_learner'):
    _orig_create = learner._create_sequential_learner
    def _tracked_create(learner_id, config):
        seq = _orig_create(learner_id, config)
        seq._register_task = _tracked_register
        return seq
    learner._create_sequential_learner = _tracked_create
```

### Pending notifications as list (deferred, discussed)
Currently `pendingNotifications[key] = data` (last-write-wins).
For fast-completing workflows, this means only the last task_event replays.
Fix: accumulate as a list:
```javascript
pendingNotifications[key] = (pendingNotifications[key] || []).concat([data]);
// and in processPendingNotification: iterate over the list
```
Noted in `claude_notifications.md`. Not urgent until task_event delivery is fixed.

---

## Test Status
```
pytest tests/unittests/   # 240 tests pass
```
Integration tests require live services.

---

## Key File Locations

| File | Purpose |
|------|---------|
| `src/radical/edge/service.py` | EdgeService + `_resolve_plugin_names` |
| `bin/radical-edge-service.py` | CLI entry point with `-p` arg |
| `src/radical/edge/plugin_base.py` | Plugin base class + registry |
| `src/radical/edge/data/edge_explorer.html` | Explorer UI |
| `rose/rose/service/api/rest.py` | RoseSession + `_run_workflow` + task tracking |
| `rose/rose/service/api/rose.js` | UI module for Explorer (task_event + workflow_state) |
| `./claude_notifications.md` | Task notification debugging notes |
