docs: define personal-agent desktop exposure contract

This commit is contained in:
Svrnty
2026-06-14 08:46:33 -04:00
parent 8274edffeb
commit 0944fc7fd0
6 changed files with 346 additions and 2 deletions
+1
View File
@@ -11,6 +11,7 @@ JP's personal assistant / chief of staff. Daily briefing, inbox triage, comms in
- **Secondbrain proposal route:** [`docs/contracts/personal-agent-secondbrain-proposal-route.json`](docs/contracts/personal-agent-secondbrain-proposal-route.json) — proposal-only personal memory intake; durable apply remains owned by Secondbrain. - **Secondbrain proposal route:** [`docs/contracts/personal-agent-secondbrain-proposal-route.json`](docs/contracts/personal-agent-secondbrain-proposal-route.json) — proposal-only personal memory intake; durable apply remains owned by Secondbrain.
- **Conductor/Curator service handoff:** [`docs/contracts/personal-agent-conductor-curator-service-handoff.json`](docs/contracts/personal-agent-conductor-curator-service-handoff.json) — redacted service map for future route selection and hygiene review pickup. - **Conductor/Curator service handoff:** [`docs/contracts/personal-agent-conductor-curator-service-handoff.json`](docs/contracts/personal-agent-conductor-curator-service-handoff.json) — redacted service map for future route selection and hygiene review pickup.
- **Runtime readiness snapshot:** [`docs/contracts/personal-agent-runtime-readiness-snapshot.json`](docs/contracts/personal-agent-runtime-readiness-snapshot.json) — redacted per-surface runtime state and gaps; aggregate readiness remains degraded. - **Runtime readiness snapshot:** [`docs/contracts/personal-agent-runtime-readiness-snapshot.json`](docs/contracts/personal-agent-runtime-readiness-snapshot.json) — redacted per-surface runtime state and gaps; aggregate readiness remains degraded.
- **Desktop exposure contract:** [`docs/contracts/personal-agent-desktop-exposure-contract.json`](docs/contracts/personal-agent-desktop-exposure-contract.json) — adapter-facing state rows for Desktop/Dashboard display; no UI wiring from this route.
- **Historical Steev reference redirect:** [`docs/STEEV-MASTER.md`](docs/STEEV-MASTER.md). - **Historical Steev reference redirect:** [`docs/STEEV-MASTER.md`](docs/STEEV-MASTER.md).
## Structure ## Structure
+5
View File
@@ -44,3 +44,8 @@ items:
status: complete status: complete
source: docs/contracts/personal-agent-runtime-readiness-snapshot.json source: docs/contracts/personal-agent-runtime-readiness-snapshot.json
owner: jp owner: jp
- id: PACR-008
title: Desktop Adapter Exposure Contract
status: complete
source: docs/contracts/personal-agent-desktop-exposure-contract.json
owner: jp
+1
View File
@@ -19,6 +19,7 @@ Active authority:
- `docs/contracts/personal-agent-secondbrain-proposal-route.json` - `docs/contracts/personal-agent-secondbrain-proposal-route.json`
- `docs/contracts/personal-agent-conductor-curator-service-handoff.json` - `docs/contracts/personal-agent-conductor-curator-service-handoff.json`
- `docs/contracts/personal-agent-runtime-readiness-snapshot.json` - `docs/contracts/personal-agent-runtime-readiness-snapshot.json`
- `docs/contracts/personal-agent-desktop-exposure-contract.json`
- `docs/prd/2026-06-14-personal-agent-context-runtime-prd.md` - `docs/prd/2026-06-14-personal-agent-context-runtime-prd.md`
- `docs/supersession/2026-06-14-personal-agent-context-runtime-supersession-register.md` - `docs/supersession/2026-06-14-personal-agent-context-runtime-supersession-register.md`
@@ -0,0 +1,168 @@
{
"schema_version": "personal-agent-desktop-exposure-contract/v1",
"status": "active-profile-desktop-exposure-contract",
"contract_id": "personal-agent-desktop-exposure-contract",
"profile_identity": "personal-agent",
"display_name": "Steev",
"observed_date": "2026-06-14",
"desktop_integration_claimed": false,
"runtime_readiness_claimed": false,
"seed_readiness_claimed": false,
"core_promotion_claimed": false,
"adapter_workspace": "../cortex-hermes-adapter",
"adapter_validator_command": "python3 tools/validate_cortex_hermes_adapter_child.py",
"adapter_validator_result_observed": "ok",
"adapter_reference_contracts": [
"../cortex-hermes-adapter/contracts/desktop-dashboard-host-surface.md",
"../cortex-hermes-adapter/contracts/personal-agent-s518-runtime-host-surface-intake.json",
"../cortex-hermes-adapter/contracts/first-open-evidence.schema.json",
"../cortex-hermes-adapter/dashboard/package-view.sample.json"
],
"authority_boundary": {
"profile_owns_desktop_exposure_contract": true,
"adapter_owns_desktop_rendering": true,
"seed_owns_package_first_open_proof": true,
"core_owns_acceptance": true,
"profile_mutates_adapter": false,
"notes": "This contract is a profile-side handoff for desktop-visible readiness. It does not wire UI or mutate the adapter workspace."
},
"allowed_adapter_surfaces": [
"package.status",
"runtime.health",
"onboarding.state",
"profile.distribution",
"capability.catalog"
],
"state_vocabulary": [
"ready",
"degraded",
"pending",
"blocked",
"disabled"
],
"desktop_rows": [
{
"row_id": "personal-agent.profile",
"label": "personal-agent",
"display_name": "Steev",
"surface": "profile.distribution",
"state": "degraded",
"source_contract": "docs/contracts/personal-agent-runtime-readiness-snapshot.json",
"visible_reason": "Profile exists and capability contracts are present, but aggregate runtime readiness is degraded."
},
{
"row_id": "personal-agent.imessage.read",
"label": "iMessage read",
"surface": "runtime.health",
"state": "ready",
"source_contract": "docs/contracts/personal-agent-bluebubbles-binding.json",
"visible_reason": "BlueBubbles package validator is OK, read-only, secondbrain-personal, and orgbrain-forbidden."
},
{
"row_id": "personal-agent.mail.read",
"label": "Proton Mail read",
"surface": "runtime.health",
"state": "degraded",
"source_contract": "docs/contracts/personal-agent-runtime-readiness-snapshot.json",
"visible_reason": "Proton MCP is enabled and bridge is running, but email gate remains exited."
},
{
"row_id": "personal-agent.calendar.read",
"label": "Proton Calendar read",
"surface": "runtime.health",
"state": "degraded",
"source_contract": "docs/contracts/personal-agent-runtime-readiness-snapshot.json",
"visible_reason": "Calendar gate is running, but Proton/rclone package child and final runtime proof are not complete."
},
{
"row_id": "personal-agent.contacts.read",
"label": "Proton Contacts read",
"surface": "runtime.health",
"state": "degraded",
"source_contract": "docs/contracts/personal-agent-runtime-readiness-snapshot.json",
"visible_reason": "Proton MCP is enabled, but contacts gate remains exited."
},
{
"row_id": "personal-agent.drive.read",
"label": "Proton Drive read",
"surface": "runtime.health",
"state": "degraded",
"source_contract": "docs/contracts/personal-agent-runtime-readiness-snapshot.json",
"visible_reason": "rclone about probe is redacted-ok, but governed wrapper and package child are not complete."
},
{
"row_id": "personal-agent.secondbrain.proposal",
"label": "Personal memory proposals",
"surface": "capability.catalog",
"state": "pending",
"source_contract": "docs/contracts/personal-agent-secondbrain-proposal-route.json",
"visible_reason": "Proposal envelope route exists; durable Secondbrain apply remains blocked."
},
{
"row_id": "personal-agent.browser.host-runtime",
"label": "Browser host runtime",
"surface": "capability.catalog",
"state": "blocked",
"source_contract": "docs/prd/2026-06-14-personal-agent-context-runtime-prd.md",
"visible_reason": "Full desktop/browser control requires separate PACR-009 approval packet."
},
{
"row_id": "personal-agent.write-actions",
"label": "Writes and sends",
"surface": "capability.catalog",
"state": "disabled",
"source_contract": "docs/contracts/personal-agent-profile-surface-contract.json",
"visible_reason": "Silent sends, deletes, mark-read/read receipts, contact/calendar/file mutation, and durable writes are denied unless a confirmation surface is explicitly approved."
}
],
"desktop_false_effects": {
"adapter_mutated_by_profile": false,
"desktop_or_dashboard_opened": false,
"runtime_started": false,
"runtime_stopped": false,
"docker_started": false,
"profile_exposure_changed": false,
"memory_domain_access_granted": false,
"provider_call": false,
"secret_value_read": false,
"raw_payload_imported": false,
"seed_release_claim": false,
"runtime_readiness_claim": false,
"public_release_claim": false
},
"memory_policy": {
"target": "secondbrain-personal",
"forbidden": [
"orgbrain"
],
"desktop_displays_memory_content": false,
"desktop_displays_redacted_state_only": true
},
"proof_policy": {
"mode": "redacted-only",
"forbidden_fields": [
"raw_messages",
"message_text",
"mail_bodies",
"mail_subjects",
"sender_address",
"recipient_address",
"contact_details",
"calendar_event_details",
"drive_file_names",
"drive_file_contents",
"attachment_content",
"endpoint_payloads",
"credentials",
"secret_values"
]
},
"remaining_gates": {
"adapter_lane_pickup": "blocked-follow-up",
"desktop_ui_wiring": "blocked-follow-up",
"seed_package_pickup": "blocked-follow-up",
"runtime_readiness_finalization": "blocked-follow-up",
"browser_host_runtime_approval": "blocked-follow-up",
"final_acceptance_packet": "blocked-follow-up"
}
}
@@ -41,7 +41,8 @@ desktop exposure must be treated as one of:
| Personal-agent Conductor/Curator service handoff | active-authority | `docs/contracts/personal-agent-conductor-curator-service-handoff.json` gives route and hygiene lanes a redacted service map | | Personal-agent Conductor/Curator service handoff | active-authority | `docs/contracts/personal-agent-conductor-curator-service-handoff.json` gives route and hygiene lanes a redacted service map |
| Conductor/curator adoption | blocked-follow-up | Owning lanes must explicitly pick up the handoff; this profile does not mutate them | | Conductor/curator adoption | blocked-follow-up | Owning lanes must explicitly pick up the handoff; this profile does not mutate them |
| Personal-agent runtime readiness snapshot | active-authority | `docs/contracts/personal-agent-runtime-readiness-snapshot.json` names per-surface states and runtime gaps without aggregate readiness claim | | Personal-agent runtime readiness snapshot | active-authority | `docs/contracts/personal-agent-runtime-readiness-snapshot.json` names per-surface states and runtime gaps without aggregate readiness claim |
| Desktop app exposure | blocked-follow-up | `PACR-008`, after adapter lane release | | Personal-agent desktop exposure contract | active-authority | `docs/contracts/personal-agent-desktop-exposure-contract.json` defines adapter-visible rows without UI wiring |
| Desktop app exposure wiring | blocked-follow-up | Owning adapter lane must pick up the contract and wire UI after approval |
| Browser/Webwright host control | blocked-follow-up | `PACR-009`, explicit approval only | | Browser/Webwright host control | blocked-follow-up | `PACR-009`, explicit approval only |
## Known Artifacts And Supersession State ## Known Artifacts And Supersession State
@@ -80,6 +81,7 @@ desktop exposure must be treated as one of:
- Graph context should expose the personal-agent Secondbrain proposal route as active while keeping durable apply blocked to Secondbrain/curator. - Graph context should expose the personal-agent Secondbrain proposal route as active while keeping durable apply blocked to Secondbrain/curator.
- Graph context should expose the personal-agent Conductor/Curator service handoff as active, while adoption remains blocked to owning lanes. - Graph context should expose the personal-agent Conductor/Curator service handoff as active, while adoption remains blocked to owning lanes.
- Graph context should expose the personal-agent runtime snapshot as degraded until the named runtime gaps close. - Graph context should expose the personal-agent runtime snapshot as degraded until the named runtime gaps close.
- Graph context should expose the personal-agent desktop exposure contract as active, while adapter UI wiring remains blocked to the adapter lane.
- Graph context should not treat legacy Cortex Proton/rclone repositories as active authority. - Graph context should not treat legacy Cortex Proton/rclone repositories as active authority.
- Graph context should not treat duplicate Proton skills as separate current product surfaces. - Graph context should not treat duplicate Proton skills as separate current product surfaces.
- Graph context should mark browser/Webwright host control as separate HITL runtime authority. - Graph context should mark browser/Webwright host control as separate HITL runtime authority.
+168 -1
View File
@@ -23,6 +23,7 @@ REQUIRED = [
"docs/contracts/personal-agent-secondbrain-proposal-route.json", "docs/contracts/personal-agent-secondbrain-proposal-route.json",
"docs/contracts/personal-agent-conductor-curator-service-handoff.json", "docs/contracts/personal-agent-conductor-curator-service-handoff.json",
"docs/contracts/personal-agent-runtime-readiness-snapshot.json", "docs/contracts/personal-agent-runtime-readiness-snapshot.json",
"docs/contracts/personal-agent-desktop-exposure-contract.json",
"docs/prd/2026-06-14-personal-agent-context-runtime-prd.md", "docs/prd/2026-06-14-personal-agent-context-runtime-prd.md",
"docs/issues/2026-06-14-personal-agent-context-runtime-work-orders.md", "docs/issues/2026-06-14-personal-agent-context-runtime-work-orders.md",
"docs/supersession/2026-06-14-personal-agent-context-runtime-supersession-register.md", "docs/supersession/2026-06-14-personal-agent-context-runtime-supersession-register.md",
@@ -130,6 +131,34 @@ REQUIRED_RUNTIME_GAPS = {
"desktop-adapter-exposure-blocked", "desktop-adapter-exposure-blocked",
} }
REQUIRED_ADAPTER_SURFACES = {
"package.status",
"runtime.health",
"onboarding.state",
"profile.distribution",
"capability.catalog",
}
REQUIRED_DESKTOP_STATE_VOCABULARY = {
"ready",
"degraded",
"pending",
"blocked",
"disabled",
}
REQUIRED_DESKTOP_ROWS = {
"personal-agent.profile",
"personal-agent.imessage.read",
"personal-agent.mail.read",
"personal-agent.calendar.read",
"personal-agent.contacts.read",
"personal-agent.drive.read",
"personal-agent.secondbrain.proposal",
"personal-agent.browser.host-runtime",
"personal-agent.write-actions",
}
def read_text(rel: str) -> str: def read_text(rel: str) -> str:
return (ROOT / rel).read_text(encoding="utf-8") return (ROOT / rel).read_text(encoding="utf-8")
@@ -167,6 +196,7 @@ def main() -> int:
"PACR-005", "PACR-005",
"PACR-006", "PACR-006",
"PACR-007", "PACR-007",
"PACR-008",
"status: candidate", "status: candidate",
"owner: jp", "owner: jp",
]: ]:
@@ -873,6 +903,142 @@ def main() -> int:
if remaining_gates.get("reboot_power_loss_drill") != "optional-follow-up": if remaining_gates.get("reboot_power_loss_drill") != "optional-follow-up":
errors.append("runtime_snapshot_reboot_gate_missing") errors.append("runtime_snapshot_reboot_gate_missing")
desktop = load_json("docs/contracts/personal-agent-desktop-exposure-contract.json", errors)
if desktop:
if desktop.get("schema_version") != "personal-agent-desktop-exposure-contract/v1":
errors.append("desktop_exposure_schema_version_invalid")
if desktop.get("status") != "active-profile-desktop-exposure-contract":
errors.append("desktop_exposure_status_invalid")
if desktop.get("contract_id") != "personal-agent-desktop-exposure-contract":
errors.append("desktop_exposure_contract_id_invalid")
if desktop.get("profile_identity") != "personal-agent":
errors.append("desktop_exposure_profile_identity_not_personal_agent")
if desktop.get("display_name") != "Steev":
errors.append("desktop_exposure_display_name_not_steev")
for key in [
"desktop_integration_claimed",
"runtime_readiness_claimed",
"seed_readiness_claimed",
"core_promotion_claimed",
]:
if desktop.get(key) is not False:
errors.append(f"desktop_exposure_overclaim:{key}")
if desktop.get("adapter_workspace") != "../cortex-hermes-adapter":
errors.append("desktop_exposure_adapter_workspace_invalid")
if desktop.get("adapter_validator_command") != "python3 tools/validate_cortex_hermes_adapter_child.py":
errors.append("desktop_exposure_adapter_validator_missing")
if desktop.get("adapter_validator_result_observed") != "ok":
errors.append("desktop_exposure_adapter_validator_not_ok")
refs = set(desktop.get("adapter_reference_contracts", []))
for ref in [
"../cortex-hermes-adapter/contracts/desktop-dashboard-host-surface.md",
"../cortex-hermes-adapter/contracts/personal-agent-s518-runtime-host-surface-intake.json",
"../cortex-hermes-adapter/contracts/first-open-evidence.schema.json",
"../cortex-hermes-adapter/dashboard/package-view.sample.json",
]:
if ref not in refs:
errors.append(f"desktop_exposure_adapter_ref_missing:{ref}")
boundary = desktop.get("authority_boundary", {})
for key in [
"profile_owns_desktop_exposure_contract",
"adapter_owns_desktop_rendering",
"seed_owns_package_first_open_proof",
"core_owns_acceptance",
]:
if boundary.get(key) is not True:
errors.append(f"desktop_exposure_boundary_missing:{key}")
if boundary.get("profile_mutates_adapter") is not False:
errors.append("desktop_exposure_profile_mutates_adapter")
adapter_surfaces = set(desktop.get("allowed_adapter_surfaces", []))
if adapter_surfaces != REQUIRED_ADAPTER_SURFACES:
errors.append("desktop_exposure_adapter_surfaces_invalid")
state_vocabulary = set(desktop.get("state_vocabulary", []))
if state_vocabulary != REQUIRED_DESKTOP_STATE_VOCABULARY:
errors.append("desktop_exposure_state_vocabulary_invalid")
rows = desktop.get("desktop_rows", [])
row_ids = {row.get("row_id") for row in rows}
missing_rows = sorted(REQUIRED_DESKTOP_ROWS - row_ids)
if missing_rows:
errors.append(f"desktop_exposure_rows_missing:{','.join(missing_rows)}")
state_by_row = {row.get("row_id"): row.get("state") for row in rows}
expected_states = {
"personal-agent.profile": "degraded",
"personal-agent.imessage.read": "ready",
"personal-agent.mail.read": "degraded",
"personal-agent.calendar.read": "degraded",
"personal-agent.contacts.read": "degraded",
"personal-agent.drive.read": "degraded",
"personal-agent.secondbrain.proposal": "pending",
"personal-agent.browser.host-runtime": "blocked",
"personal-agent.write-actions": "disabled",
}
for row_id, expected in expected_states.items():
if state_by_row.get(row_id) != expected:
errors.append(f"desktop_exposure_row_state_invalid:{row_id}")
for row in rows:
row_id = row.get("row_id", "<unknown>")
if row.get("surface") not in REQUIRED_ADAPTER_SURFACES:
errors.append(f"desktop_exposure_row_surface_invalid:{row_id}")
if row.get("state") not in REQUIRED_DESKTOP_STATE_VOCABULARY:
errors.append(f"desktop_exposure_row_state_not_vocab:{row_id}")
if not row.get("source_contract"):
errors.append(f"desktop_exposure_row_source_missing:{row_id}")
if not row.get("visible_reason"):
errors.append(f"desktop_exposure_row_reason_missing:{row_id}")
false_effects = desktop.get("desktop_false_effects", {})
for effect in [
"adapter_mutated_by_profile",
"desktop_or_dashboard_opened",
"runtime_started",
"runtime_stopped",
"docker_started",
"profile_exposure_changed",
"memory_domain_access_granted",
"provider_call",
"secret_value_read",
"raw_payload_imported",
"seed_release_claim",
"runtime_readiness_claim",
"public_release_claim",
]:
if false_effects.get(effect) is not False:
errors.append(f"desktop_exposure_false_effect_not_false:{effect}")
memory = desktop.get("memory_policy", {})
if memory.get("target") != "secondbrain-personal":
errors.append("desktop_exposure_memory_target_not_secondbrain_personal")
if "orgbrain" not in memory.get("forbidden", []):
errors.append("desktop_exposure_orgbrain_not_forbidden")
if memory.get("desktop_displays_memory_content") is not False:
errors.append("desktop_exposure_displays_memory_content")
if memory.get("desktop_displays_redacted_state_only") is not True:
errors.append("desktop_exposure_not_redacted_state_only")
forbidden_fields = set(desktop.get("proof_policy", {}).get("forbidden_fields", []))
for field in [
"raw_messages",
"message_text",
"mail_bodies",
"contact_details",
"calendar_event_details",
"drive_file_names",
"drive_file_contents",
"endpoint_payloads",
"credentials",
"secret_values",
]:
if field not in forbidden_fields:
errors.append(f"desktop_exposure_forbidden_field_missing:{field}")
remaining_gates = desktop.get("remaining_gates", {})
for gate in [
"adapter_lane_pickup",
"desktop_ui_wiring",
"seed_package_pickup",
"runtime_readiness_finalization",
"browser_host_runtime_approval",
"final_acceptance_packet",
]:
if remaining_gates.get(gate) != "blocked-follow-up":
errors.append(f"desktop_exposure_remaining_gate_missing:{gate}")
for rel in ["AGENT.md", "CONTRACT.md", "DISCLOSURE.md", "README.md", "docs/STEEV-MASTER.md"]: for rel in ["AGENT.md", "CONTRACT.md", "DISCLOSURE.md", "README.md", "docs/STEEV-MASTER.md"]:
path = ROOT / rel path = ROOT / rel
if not path.exists(): if not path.exists():
@@ -895,6 +1061,7 @@ def main() -> int:
"Personal-agent Secondbrain proposal route", "Personal-agent Secondbrain proposal route",
"Personal-agent Conductor/Curator service handoff", "Personal-agent Conductor/Curator service handoff",
"Personal-agent runtime readiness snapshot", "Personal-agent runtime readiness snapshot",
"Personal-agent desktop exposure contract",
"superseded", "superseded",
"legacy-reference", "legacy-reference",
"blocked-follow-up", "blocked-follow-up",
@@ -910,7 +1077,7 @@ def main() -> int:
result = { result = {
"ok": not errors, "ok": not errors,
"validator": "personal-agent-profile-distribution-v6", "validator": "personal-agent-profile-distribution-v7",
"checked": REQUIRED, "checked": REQUIRED,
"errors": errors, "errors": errors,
"warnings": [], "warnings": [],