Sibling ticket to oncofiles#452
Oncofiles v5.12 plans to ship a schema change on the `agent_state` table (migration 061) that adds a `patient_id` column. This ticket tracks Oncoteam's side of the coordinated rollout.
Oncofiles meta: oncofiles#453 (v5.12 Sprint)
Architecture memo: `docs/architecture/option_a_scoping_decisions.md` in Oncofiles repo
Blast radius: touches both projects — Oncoteam is the primary writer of `agent_state`
Current state (pre-migration)
Oncofiles' `agent_state` table has:
- `agent_id` (e.g. 'oncoteam')
- `key` (e.g. 'last_briefing_date')
- `value` (JSON string)
- UNIQUE constraint on `(agent_id, key)`
The latent leak: if an Oncoteam agent sets `agent_state('last_briefing_date', '2026-01-15')` while processing Patient A, then switches to Patient B, the `get_agent_state('last_briefing_date')` call returns Patient A's date. Classic cross-patient state collision.
Originally identified as "Finding 6" in the closed oncofiles#406 thread but never individually ticketed.
Post-migration state
Oncofiles migration 061 will:
- `ALTER TABLE agent_state ADD COLUMN patient_id TEXT`
- DROP the UNIQUE index on `(agent_id, key)`
- CREATE a new UNIQUE index on `(agent_id, key, patient_id)`
- Update 3 Oncofiles MCP tools (`set_agent_state`, `get_agent_state`, `list_agent_states`) to accept `patient_slug` per Option A
What Oncoteam needs to do
1. Update every `set_agent_state` / `get_agent_state` / `list_agent_states` caller
Audit Oncoteam's codebase for all `call_oncofiles('set_agent_state', ...)` and `call_oncofiles('get_agent_state', ...)` invocations. Each must pass `patient_slug` matching the currently-active patient for the agent task.
Today's pattern (unscoped):
```python
call_oncofiles('set_agent_state', {'key': 'last_briefing_date', 'value': '2026-04-21'})
```
Post-migration pattern (scoped):
```python
call_oncofiles('set_agent_state', {'key': 'last_briefing_date', 'value': '2026-04-21', 'patient_slug': patient_slug})
```
2. Decide the legacy-data policy
Oncofiles migration 061 has two options for existing rows:
- (a) Populate `patient_id` on existing rows with the default patient (preserves data, risks wrong attribution if multi-patient agents wrote to same key)
- (b) NULL out existing rows with log line per affected agent (safest, forces re-write, acknowledges legacy data is untrustworthy)
Oncoteam preference needed. The safer option (b) means any Oncoteam agent state built up since deployment will be wiped; agents must rebuild their state per-patient on first run post-migration. Option (a) preserves history but may tie a value to the wrong patient.
3. Schedule coordinated deploy
Sequence matters:
- Oncofiles ships migration 061 + updated MCP tools (accepts both slug and legacy no-slug callers during grace period)
- Oncoteam updates all callers to pass `patient_slug`
- Oncofiles sets `DEPRECATED_LEGACY_AGENT_STATE=true` (starts warning on no-slug callers)
- After 1 week of zero warnings: Oncofiles removes legacy fallback
- Close oncofiles#452 + this ticket
4. Pattern to mirror for Oncoteam's own agent_state-like tables
If Oncoteam has any similar `(agent_id, key) → value` tables scoped globally rather than per-patient, audit them now. Same leak risk.
Timeline
References
- Oncofiles sprint: oncofiles#453 (v5.12 meta)
- Scoping memo: oncofiles `docs/architecture/option_a_scoping_decisions.md`
- Original "Finding 6" context: oncofiles#406 (closed)
Sibling ticket to oncofiles#452
Oncofiles v5.12 plans to ship a schema change on the `agent_state` table (migration 061) that adds a `patient_id` column. This ticket tracks Oncoteam's side of the coordinated rollout.
Oncofiles meta: oncofiles#453 (v5.12 Sprint)
Architecture memo: `docs/architecture/option_a_scoping_decisions.md` in Oncofiles repo
Blast radius: touches both projects — Oncoteam is the primary writer of `agent_state`
Current state (pre-migration)
Oncofiles' `agent_state` table has:
The latent leak: if an Oncoteam agent sets `agent_state('last_briefing_date', '2026-01-15')` while processing Patient A, then switches to Patient B, the `get_agent_state('last_briefing_date')` call returns Patient A's date. Classic cross-patient state collision.
Originally identified as "Finding 6" in the closed oncofiles#406 thread but never individually ticketed.
Post-migration state
Oncofiles migration 061 will:
What Oncoteam needs to do
1. Update every `set_agent_state` / `get_agent_state` / `list_agent_states` caller
Audit Oncoteam's codebase for all `call_oncofiles('set_agent_state', ...)` and `call_oncofiles('get_agent_state', ...)` invocations. Each must pass `patient_slug` matching the currently-active patient for the agent task.
Today's pattern (unscoped):
```python
call_oncofiles('set_agent_state', {'key': 'last_briefing_date', 'value': '2026-04-21'})
```
Post-migration pattern (scoped):
```python
call_oncofiles('set_agent_state', {'key': 'last_briefing_date', 'value': '2026-04-21', 'patient_slug': patient_slug})
```
2. Decide the legacy-data policy
Oncofiles migration 061 has two options for existing rows:
Oncoteam preference needed. The safer option (b) means any Oncoteam agent state built up since deployment will be wiped; agents must rebuild their state per-patient on first run post-migration. Option (a) preserves history but may tie a value to the wrong patient.
3. Schedule coordinated deploy
Sequence matters:
4. Pattern to mirror for Oncoteam's own agent_state-like tables
If Oncoteam has any similar `(agent_id, key) → value` tables scoped globally rather than per-patient, audit them now. Same leak risk.
Timeline
References