Skip to content

Navigation Menu

Sign in
Sign up

feat(desktop): add device-local agent hosting policy - #7419

Open
lechnertech-mike wants to merge 4 commits into
block:main from
lechnertech-mike:codex/device-agent-hosting-policy
Open

feat(desktop): add device-local agent hosting policy #7419
lechnertech-mike wants to merge 4 commits into
block:main from
lechnertech-mike:codex/device-agent-hosting-policy

Conversation

@lechnertech-mike

@lechnertech-mike lechnertech-mike commented Sep 6, 2026
edited
Loading

Copy link
Copy Markdown

Summary

When two Desktops use the same owner account, the second installation can create another instance of an agent already hosted elsewhere. Removing a copied definition can then publish a deletion to the shared account. This adds an opt-in device policy for a secondary Desktop: use remote agents in client-only mode, or host additional agents with distinct names while protecting explicitly selected remote identities.

  • Add local Client-only mode and Unique agent names settings. Native guards cover identity creation, imports, starts, deployment, definition management, and startup restoration. Changes take effect after restart; an absent policy preserves current behavior, and an unreadable policy refuses local execution.
  • In unique-name mode, check new names and actual renames against local records and the owner-verified relay directory. Keep unchanged-name configuration edits usable offline. Protected names, public keys, and definition IDs cannot be managed locally.
  • Keep runnable definitions, team templates, and their pending outgoing backlog on the device. Publish only ownership/lifecycle records for explicitly registered local identities. Client-only mode pauses all outgoing definition reconciliation. Existing records and queues are retained.
  • Allow optional exact identity preferences for new discovery selections while preserving historical public-key links. No private keys are copied between devices.

Related issue

Related to #2648, #3440, and #5319. I searched existing issues and PRs before submission. The closest overlapping work is #6883 (adding existing remote agents to channels) and #6259 (showing the host computer). This proposal adds device-local execution and outgoing synchronization controls; it does not replace that discovery or host-label work.

Setup and scope

Existing remote identities must be entered explicitly in agents/agent-device-policy.json as preferred_agents. The UI discloses that an empty list does not stop copied local/autostart records; client-only mode is available to stop all local hosting until bindings are configured. See the configuration guide.

Local hosting reservations span the installation; the binding owner/community scopes discovery, not separate local hosting namespaces.

This is not a relay-wide lock or atomic name reservation: another unconfigured client can still create agents, including during simultaneous creation. Team imports/catalog publication require unrestricted hosting. Disabling both restrictions resumes retained synchronization after restart, as disclosed in Settings. Maintainer feedback on whether this opt-in policy fits the intended multi-device model would be welcome.

Testing

  • just ci passed on 76a1a6e4: formatting/lint/static gates, workspace unit tests, desktop/web builds, 6,456 desktop tests, 3,204 native desktop tests (19 ignored), and 2,072 mobile tests.
  • Previously validated browser coverage on d6144b19 (frontend unchanged by the review follow-up): pnpm exec playwright test tests/e2e/client-only-agents.spec.ts tests/e2e/remote-owned-mentions.spec.ts tests/e2e/mention-recipients.spec.ts --project=smoke --workers=1: 45 passed.
  • Targeted outbound guard tests: 16 passed; selective-sync loopback tests: 2 passed. The new test-only receiver is registered in the existing egress inventory; production submission continues through the guarded funnel.
  • Review regression coverage includes deletion enrollment before removal, registration failure and retry, atomic retention rollback, registry capacity, preferred identity retrieval outside the first search page, ownership/signature checks, and lossless bounded pagination.
  • Additional regressions cover preferred-key validation/canonicalization and client-only startup housekeeping before restore suppression.
  • git diff --check passed. Tests used isolated fixtures with live Buzz credentials removed from the test environment.

The patch received an independent code review, including fresh runs of the nine policy model tests and six Settings component tests. Regression coverage includes an offline configuration edit that preserves the name, an offline rename that is refused, and the empty-protection disclosure.

Manual verification procedure for two test installations using the same owner account:

  1. Keep an agent running on installation A. On B, enable client-only mode and restart; verify its inventory cannot create/start/deploy local agents and that A's existing identity remains available for invitations and mentions.
  2. Quit B and configure A's exact identity/definition bindings. Enable unique names with client-only mode off, restart, and verify A's protected identity cannot start or be renamed/deleted locally.
  3. Create an unrelated name on B and verify it can run. Attempt the protected name or a same-name relay identity and verify creation is refused.
  4. Make the relay unavailable. Save a local agent's prompt/model configuration without renaming it, then verify an actual rename is refused until the directory can be checked.

The automated browser checks and screenshots use isolated mock data. Live two-device delivery and runtime execution have not been verified for this PR revision.

UI

Before, on the current main tree:

Agent settings before

After, with unique-name mode and an explicit remote identity preference:

Agent settings after

After, showing the disclosure when no existing identities are protected:

Empty protection disclosure

Signed-off-by: Michael Lechner <25083998+lechnertech-mike@users.noreply.github.com>
Signed-off-by: Michael Lechner <25083998+lechnertech-mike@users.noreply.github.com>

github-actions Bot commented Sep 6, 2026
edited
Loading

Copy link
Copy Markdown

🔐 Codex Security Review

Status: review required for the current range.

The current range is 3c7f288c60d67df78577b237e27c3dfc8831aaa1...76a1a6e40c3dd3c9c3018748aa1515d8b96ebe3a.
A new review must complete for this exact range. When manual authorization
is required, a Block organization member must comment exactly
@buzz-security-review 76a1a6e40c3dd3c9c3018748aa1515d8b96ebe3a to authorize a new review.
Any previous review applies only to its recorded range.

chatgpt-codex-connector Bot commented Sep 6, 2026
edited
Loading

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026年09月06日T16:30:56.168886Z 76a1a6e Manual request
i️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d6144b1983

i️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread desktop/src-tauri/src/managed_agents/persona_events.rs
Comment thread desktop/src-tauri/src/commands/agents_pending.rs Outdated
Comment thread desktop/src-tauri/src/managed_agents/device_policy/sync.rs Outdated
Comment thread desktop/src-tauri/src/commands/profile.rs Outdated
Signed-off-by: Michael Lechner <25083998+lechnertech-mike@users.noreply.github.com>

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 99a6ef5d92

i️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +73 to +76
&& self.preferred_agents.iter().any(|agent| {
agent.name.trim().eq_ignore_ascii_case(name.trim())
|| pubkey.is_some_and(|key| key.eq_ignore_ascii_case(&agent.pubkey))
|| persona_id.is_some_and(|id| agent.persona_id.as_deref() == Some(id))

@chatgpt-codex-connector chatgpt-codex-connector Bot Sep 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scope protected identities to their configured community

Pass the active relay and owner into this guard before matching a preferred entry. As written, relay_url and owner_pubkey are ignored, so after configuring Scout for owner A on community A, switching the same Desktop to owner/community B still prevents creating, editing, or hosting an unrelated Scout there. This makes a community-scoped binding behave as an installation-wide name reservation.

Useful? React with 👍 / 👎.

@lechnertech-mike lechnertech-mike Sep 6, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This guard intentionally uses the installation-wide local hosting namespace. unique_names::preflight also rejects same-name local records and personas before querying the owner/community directory. Scoping the entire guard would additionally allow an exact protected key or definition to become locally runnable after an account/community switch.

relay_url and owner_pubkey scope discovery preferences; they do not partition the local hosting catalog. I clarified this explicitly in docs/agent-device-policy.md in 76a1a6e. I am keeping that behavior in this PR. Supporting unrelated same-name local agents across communities would require a consistent change to the local catalog collision rules as well as this guard.

if bytes.len() > 65_536 {
return Err("Agent device policy exceeds 64 KiB".into());
}
serde_json::from_slice(&bytes).map_err(|error| format!("Invalid agent device policy: {error}"))

@chatgpt-codex-connector chatgpt-codex-connector Bot Sep 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate preferred public keys when loading the policy

Reject semantically invalid preferred bindings here rather than accepting any strings that deserialize. The documented setup requires hand-editing this file, and a typo in preferred_agents[].pubkey is later forwarded by complete_search as a Nostr authors filter; the relay cannot deserialize that filter and the entire matching people search fails instead of reporting a recoverable policy-load error.

Useful? React with 👍 / 👎.

@lechnertech-mike lechnertech-mike Sep 6, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 76a1a6e. Policy loading now validates both preferred public-key fields and canonicalizes valid hex values. Malformed values return a recoverable policy-load error identifying the binding index and field, without forwarding them into a relay authors filter or echoing the supplied value.

The regression reproduced accepting an invalid public key before the fix and now covers malformed agent/owner keys plus uppercase normalization. Full just ci passed, and the final diff received independent review.

Comment on lines +99 to +101
if super::device_policy::is_client_only(app) {
return Ok(());
}

@chatgpt-codex-connector chatgpt-codex-connector Bot Sep 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reap existing processes before skipping client-only restore

Run the restore housekeeping before returning for client-only mode, and suppress only the later spawn phase. If the user saves client-only mode and Desktop crashes before graceful shutdown, the next launch returns here before kill_stale_tracked_processes and the three orphan sweeps, allowing the old agent process tree to keep responding and accessing local resources until the periodic sweeper eventually catches it one or two intervals later.

AGENTS.md reference: AGENTS.md:L194-L202

Useful? React with 👍 / 👎.

@lechnertech-mike lechnertech-mike Sep 6, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 76a1a6e. Client-only mode now executes the existing tracked-process cleanup and orphan sweeps before suppressing restoration. Changed lifecycle state is saved; no restore candidates proceed to the spawn phase. Errors returned by the preparation step propagate.

Regression tests exercise the production cleanup/restore decision seam and reproduced both skipped housekeeping and swallowed cleanup errors before the fix. They verify ordering and suppression; they do not claim a live crash/process-termination test. Full just ci passed, and the final diff received independent review.

Signed-off-by: Michael Lechner <25083998+lechnertech-mike@users.noreply.github.com>

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 76a1a6e40c

i️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +21 to +22
if crate::managed_agents::device_policy::pauses_sync(app) {
return Ok(());

@chatgpt-codex-connector chatgpt-codex-connector Bot Sep 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve deletion recovery while selective sync is active

When tombstone_managed_agent_pending hits a transient SQLite error after the managed-agent record has already been removed, it still logs and swallows that failure; this new early return also disables reconcile_deleted_heads, so unique-name mode has no later path that can reconstruct the missing kind:5/kind:9035 work and the deleted agent can remain visible indefinitely. The fresh evidence beyond the earlier registry finding is that eligibility is now prepared, but the actual deletion-journal write can still fail without propagation or retry.

AGENTS.md reference: AGENTS.md:L167-L174

Useful? React with 👍 / 👎.

state: State<'_, AppState>,
app: tauri::AppHandle,
) -> Result<SearchUsersResponse, String> {
let policy = crate::managed_agents::device_policy::active(&app)?;

@chatgpt-codex-connector chatgpt-codex-connector Bot Sep 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep people discovery usable after policy load failures

If the manually edited device-policy file is malformed, unreadable, or oversized, active() returns its cached error here before any relay request. Consequently ordinary people search—including mention autocomplete and recipient pickers—fails completely rather than merely disabling local hosting; list_relay_agents and list_personas propagate the same error. Treat policy-load failures as fail-closed for execution while allowing these non-hosting reads to proceed without preference filtering so users can still interact with remote identities and recover in Settings.

Useful? React with 👍 / 👎.

Comment on lines +171 to +175
let existing_key = linked.first().map(|record| record.pubkey.as_str());
crate::managed_agents::device_policy::active(&app)?
.check_name_update(
&current_name,
&input.display_name,

@chatgpt-codex-connector chatgpt-codex-connector Bot Sep 6, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject persona renames that duplicate linked identities

When unique-name mode is enabled on an installation that already has multiple instances linked to the same persona and more than one still carries the persona's display name, this preflight validates only the current records against the proposed name. Because none has the new name yet, the check passes, and propagate_persona_name_rename subsequently assigns that same new name to every matching linked record, creating duplicate local identities despite unique-name mode. Validate the complete prospective cascade and reject a rename that would assign one name to multiple pubkeys.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@chatgpt-codex-connector chatgpt-codex-connector[bot] chatgpt-codex-connector[bot] left review comments

At least 1 approving review is required to merge this pull request.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

1 participant

AltStyle によって変換されたページ (->オリジナル) /