Skip to content

Commit a1295fd

Browse files
Merge pull request #37 from MichaelWave369/codex/integrate-agentora-with-phios-and-agentception-5oyh8w
Codex-generated pull request
2 parents f5f9461 + 0e6772c commit a1295fd

9 files changed

Lines changed: 482 additions & 9 deletions

File tree

.env.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,3 +172,10 @@ AGENTORA_MISSIONS_REPLAY_MAX_LINEAGE_DEPTH=20
172172
AGENTORA_MISSIONS_REPLAY_REQUIRE_PROVENANCE_NOTE=false
173173
AGENTORA_MISSIONS_SIGN_EXPORTS=false
174174
AGENTORA_MISSIONS_EXPORT_SIGNING_KEY=
175+
176+
# Persona policy hooks (Phase J)
177+
AGENTORA_PERSONA_POLICY_ENABLED=false
178+
AGENTORA_PERSONA_POLICY_REQUIRE_DUAL_REVIEW_ON_HIGH_RISK=false
179+
AGENTORA_PERSONA_POLICY_BLOCK_EXPLORATORY_ON_HIGH_RISK=false
180+
AGENTORA_PERSONA_POLICY_REQUIRE_OVERRIDE_REASON=true
181+
AGENTORA_PERSONA_POLICY_REQUIRE_CONSERVATIVE_BRANCH_ON_HIGH_RISK=false

README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,3 +500,52 @@ Persona recommendations are heuristic operator aids. They are not objective trut
500500
3. Open persona portfolio + persona summary panels.
501501
4. Apply override actions (`accept`, `reject`, `manual`) with rationale.
502502
5. Use persona insights to inspect score/risk/PR/writeback trends.
503+
504+
## PhiOS + AgentCeption integration (Phase J)
505+
506+
Phase J adds cross-root persona intelligence and first-class operator decision auditability.
507+
508+
### Phase J capabilities
509+
510+
- Persisted operator decision events (`recommendation_accepted`, `recommendation_rejected`, `shortlist_applied`, `eliminate_applied`, `override_applied`, `override_removed`, `persona_assignment_changed`, `policy_blocked_action`).
511+
- Persona-delta compare view with compact field deltas and heuristic interpretation notes.
512+
- Cross-root persona trends (7d/30d/all) with filters and metrics.
513+
- Persona × strategy matrix analytics for score/risk/shortlist/PR/override behavior.
514+
- Optional persona policy hooks (dual review, exploratory block on high risk, override rationale requirement, conservative-branch requirement).
515+
- Audit-focused timeline integration so operator-vs-system decisions are visible.
516+
517+
### Phase J routes
518+
519+
- `GET /api/integrations/runs/{run_id}/decision-events`
520+
- `GET /api/integrations/runs/{run_id}/persona-compare`
521+
- `GET /api/integrations/persona-trends`
522+
- `GET /api/integrations/persona-trends/matrix`
523+
- `POST /api/integrations/runs/{run_id}/policy-check`
524+
- `GET /api/integrations/runs/{run_id}/audit-summary`
525+
- `GET /api/integrations/lineage/{root_run_id}/decision-audit`
526+
527+
### Persona policy flags
528+
529+
```bash
530+
AGENTORA_PERSONA_POLICY_ENABLED=false
531+
AGENTORA_PERSONA_POLICY_REQUIRE_DUAL_REVIEW_ON_HIGH_RISK=false
532+
AGENTORA_PERSONA_POLICY_BLOCK_EXPLORATORY_ON_HIGH_RISK=false
533+
AGENTORA_PERSONA_POLICY_REQUIRE_OVERRIDE_REASON=true
534+
AGENTORA_PERSONA_POLICY_REQUIRE_CONSERVATIVE_BRANCH_ON_HIGH_RISK=false
535+
```
536+
537+
### Lifecycle
538+
539+
Root Mission -> Persona Branches -> Recommendation -> Operator Decision -> Audit Trail -> Cross-Root Trends
540+
541+
### Heuristic caveat
542+
543+
Persona analytics, compare notes, and recommendations are operator aids only and are not objective truth.
544+
545+
### Local demo steps
546+
547+
1. Create persona branches from a root run.
548+
2. Apply shortlist/eliminate/override actions.
549+
3. Review decision events and audit summary.
550+
4. Run persona compare and policy-check panels.
551+
5. Inspect persona trends and persona×strategy matrix panels.

server/app/core/config.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,12 @@ class Settings(BaseSettings):
140140
agentora_missions_sign_exports: bool = Field(default=False, alias='AGENTORA_MISSIONS_SIGN_EXPORTS')
141141
agentora_missions_export_signing_key: str = Field(default='', alias='AGENTORA_MISSIONS_EXPORT_SIGNING_KEY')
142142

143+
agentora_persona_policy_enabled: bool = Field(default=False, alias='AGENTORA_PERSONA_POLICY_ENABLED')
144+
agentora_persona_policy_require_dual_review_on_high_risk: bool = Field(default=False, alias='AGENTORA_PERSONA_POLICY_REQUIRE_DUAL_REVIEW_ON_HIGH_RISK')
145+
agentora_persona_policy_block_exploratory_on_high_risk: bool = Field(default=False, alias='AGENTORA_PERSONA_POLICY_BLOCK_EXPLORATORY_ON_HIGH_RISK')
146+
agentora_persona_policy_require_override_reason: bool = Field(default=True, alias='AGENTORA_PERSONA_POLICY_REQUIRE_OVERRIDE_REASON')
147+
agentora_persona_policy_require_conservative_branch_on_high_risk: bool = Field(default=False, alias='AGENTORA_PERSONA_POLICY_REQUIRE_CONSERVATIVE_BRANCH_ON_HIGH_RISK')
148+
143149

144150
@property
145151
def allowed_tool_names(self) -> set[str]:

server/app/integrations/schemas.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,21 @@ class AlertEventRecord(BaseModel):
281281
created_at: datetime
282282

283283

284+
class OperatorDecisionEventRecord(BaseModel):
285+
id: int
286+
run_id: int
287+
root_run_id: int
288+
created_at: datetime
289+
event_type: str
290+
actor_type: str = 'operator'
291+
previous_state_json: str = '{}'
292+
new_state_json: str = '{}'
293+
rationale: str = ''
294+
related_persona_id: str = ''
295+
related_strategy: str = ''
296+
metadata_json: str = '{}'
297+
298+
284299
class ReplayDraftRequest(BaseModel):
285300
replay_kind: str = 'exact_replay'
286301
mission_title: str | None = None
@@ -427,6 +442,10 @@ class PortfolioDecisionRequest(BaseModel):
427442
note: str = ''
428443

429444

445+
class PersonaPolicyCheckRequest(BaseModel):
446+
action: str = 'writeback'
447+
448+
430449
class LineageNode(BaseModel):
431450
id: int
432451
parent_run_id: int | None = None

server/app/models.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,21 @@ class AlertEvent(SQLModel, table=True):
429429
created_at: datetime = Field(default_factory=datetime.utcnow)
430430

431431

432+
class OperatorDecisionEvent(SQLModel, table=True):
433+
id: Optional[int] = Field(default=None, primary_key=True)
434+
run_id: int
435+
root_run_id: int
436+
created_at: datetime = Field(default_factory=datetime.utcnow)
437+
event_type: str
438+
actor_type: str = 'operator'
439+
previous_state_json: str = '{}'
440+
new_state_json: str = '{}'
441+
rationale: str = ''
442+
related_persona_id: str = ''
443+
related_strategy: str = ''
444+
metadata_json: str = '{}'
445+
446+
432447
class IntegrationSetting(SQLModel, table=True):
433448
name: str = Field(primary_key=True)
434449
enabled: bool = False

server/app/routers/integrations.py

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
from app.db import get_session
99
from app.integrations.agentception_client import AgentCeptionClient
1010
from app.integrations.phios_client import IntegrationClientError, PhiOSClient
11-
from app.integrations.schemas import BranchSetCreateRequest, ContextPackRequest, DecisionStateRequest, LaunchMissionRequest, PersonaBranchSetCreateRequest, PortfolioDecisionRequest, PrepareMissionRequest, ReplayDraftRequest, ReplayLaunchRequest, SoftwareTaskRequest, WritebackRequest
12-
from app.models import IntegrationSetting, Message, Run
11+
from app.integrations.schemas import BranchSetCreateRequest, ContextPackRequest, DecisionStateRequest, LaunchMissionRequest, PersonaBranchSetCreateRequest, PersonaPolicyCheckRequest, PortfolioDecisionRequest, PrepareMissionRequest, ReplayDraftRequest, ReplayLaunchRequest, SoftwareTaskRequest, WritebackRequest
12+
from app.models import IntegrationRun, IntegrationSetting, Message, Run
1313
from app.services.adapters.integrations import statuses
1414
from app.services.integration_orchestrator import IntegrationOrchestrator
1515

@@ -301,6 +301,21 @@ def integration_eliminate(run_id: int, payload: DecisionStateRequest, session: S
301301
raise HTTPException(status_code=400, detail=str(exc)) from exc
302302

303303

304+
305+
@router.get('/api/integrations/lineage/{root_run_id}/decision-audit')
306+
def integration_lineage_decision_audit(root_run_id: int, session: Session = Depends(get_session)):
307+
try:
308+
descendants = IntegrationOrchestrator(session).get_descendants(root_run_id)
309+
run_ids = [root_run_id] + [d['id'] for d in descendants]
310+
events = []
311+
for rid in run_ids:
312+
events.extend(IntegrationOrchestrator(session).list_operator_decision_events(rid, limit=50))
313+
events = sorted(events, key=lambda x: x.get('created_at', ''), reverse=True)
314+
return {'root_run_id': root_run_id, 'events': events[:200]}
315+
except IntegrationClientError as exc:
316+
raise HTTPException(status_code=400, detail=str(exc)) from exc
317+
318+
304319
@router.get('/api/integrations/runs/{run_id}/provenance')
305320
def integration_provenance(run_id: int, session: Session = Depends(get_session)):
306321
try:
@@ -309,6 +324,41 @@ def integration_provenance(run_id: int, session: Session = Depends(get_session))
309324
raise HTTPException(status_code=400, detail=str(exc)) from exc
310325

311326

327+
@router.get('/api/integrations/runs/{run_id}/decision-events')
328+
def integration_decision_events(run_id: int, limit: int = 100, session: Session = Depends(get_session)):
329+
try:
330+
return {'events': IntegrationOrchestrator(session).list_operator_decision_events(run_id, limit=limit)}
331+
except IntegrationClientError as exc:
332+
raise HTTPException(status_code=400, detail=str(exc)) from exc
333+
334+
335+
@router.get('/api/integrations/runs/{run_id}/persona-compare')
336+
def integration_persona_compare(run_id: int, other_run_id: int | None = None, session: Session = Depends(get_session)):
337+
try:
338+
return IntegrationOrchestrator(session).get_persona_delta_compare(run_id, other_run_id)
339+
except IntegrationClientError as exc:
340+
raise HTTPException(status_code=400, detail=str(exc)) from exc
341+
342+
343+
@router.post('/api/integrations/runs/{run_id}/policy-check')
344+
def integration_policy_check(run_id: int, payload: PersonaPolicyCheckRequest, session: Session = Depends(get_session)):
345+
row = IntegrationOrchestrator(session).get_run(run_id)
346+
if not row:
347+
raise HTTPException(status_code=404, detail='run not found')
348+
real = session.get(IntegrationRun, run_id)
349+
if not real:
350+
raise HTTPException(status_code=404, detail='run not found')
351+
return IntegrationOrchestrator(session).evaluate_persona_policy(real, action=payload.action)
352+
353+
354+
@router.get('/api/integrations/runs/{run_id}/audit-summary')
355+
def integration_audit_summary(run_id: int, session: Session = Depends(get_session)):
356+
try:
357+
return IntegrationOrchestrator(session).get_audit_summary(run_id)
358+
except IntegrationClientError as exc:
359+
raise HTTPException(status_code=400, detail=str(exc)) from exc
360+
361+
312362
@router.post('/api/integrations/runs/{run_id}/refresh')
313363
def integration_refresh(run_id: int, session: Session = Depends(get_session)):
314364
try:
@@ -384,6 +434,16 @@ def integration_persona_insights(root_run_id: int | None = None, session: Sessio
384434
return IntegrationOrchestrator(session).get_persona_performance_summary(root_run_id)
385435

386436

437+
@router.get('/api/integrations/persona-trends')
438+
def integration_persona_trends(window: str = '30d', repo: str | None = None, strategy: str | None = None, status: str | None = None, session: Session = Depends(get_session)):
439+
return IntegrationOrchestrator(session).get_persona_trends(window=window, repo=repo, strategy=strategy, status=status)
440+
441+
442+
@router.get('/api/integrations/persona-trends/matrix')
443+
def integration_persona_matrix(window: str = '30d', repo: str | None = None, status: str | None = None, session: Session = Depends(get_session)):
444+
return IntegrationOrchestrator(session).get_persona_strategy_matrix(window=window, repo=repo, status=status)
445+
446+
387447
@router.get('/api/integrations/cohorts')
388448
def integration_cohorts(
389449
group_by: str = 'repo',

0 commit comments

Comments
 (0)