Skip to content

Let a system daemon manage Pi for one configured user - #196

Open
sebbycorp wants to merge 1 commit into
mainfrom
feat/pi-system-daemon
Open

sebbycorp wants to merge 1 commit into
mainfrom
feat/pi-system-daemon

Conversation

@sebbycorp

Copy link
Copy Markdown
Collaborator

Why

Pi keeps its configuration only in the user's ~/.pi/agent, so a system daemon skipped it. Claude Desktop, Codex and Grok are system-only, though. A device that wants all four managed needed two daemons: a system one, plus a user-mode one just for Pi.

What

  • Opt-in for a system daemon. It manages Pi when daemon.pi.models and daemon.pi.settings name one user's files:
    daemon:
      pi:
        models: /home/dev/.pi/agent/models.json
        settings: /home/dev/.pi/agent/settings.json
    Setting only one of them is rejected. Without them, the system daemon still skips Pi as before.
  • Files belong to the user. The plan hands Pi's files, their .agentdesktop state sidecars and any directories it creates to the owner of the nearest existing ancestor. Pi can read them, and the !agentdesktop … credential command stays in user-private (0600) files.
  • ReconcilePlan gets an optional plan-wide owner (set_owner, Unix only). It's applied after each write and to directories the plan creates. A write isn't skipped as "already in place" while the file's owner differs.
  • Pi gets manage_for_user. Reconciler::new sets it only for a system daemon constructed with both paths, so the opt-in is explicit rather than inferred from the presence of paths.
  • Docs: the PiConfig and PiStartupConfig docs are updated, plus schema/daemon-config.{json,md}.

Tests

  • system_mode_manages_a_configured_users_pi_files (new): a system daemon writes the files, and the files and created directories have the ancestor's owner.
  • system_mode_rejects_a_single_pi_path (new).
  • pi_startup_paths_resolve_in_both_modes (was …_require_user_mode).
  • skips_configured_pi_in_system_mode and system_mode_leaves_user_pi_files_untouched still cover the skip when not opted in.
  • cargo fmt --check and cargo clippy --workspace --all-targets -D warnings are clean. cargo test -p agentdesktop-agent -p agentdesktop-core: 473 + 11 passed, run as a non-root user. I couldn't run the testcontainers integration suite locally (no Docker socket in my build container).

Manual check on an Ubuntu 24.04 VM. I ran a root agentdesktop daemon --once with daemon.pi paths for a fresh user:

  • It created ~/.pi and ~/.pi/agent, models.json (0600), settings.json and both sidecars, all owned by that user.
  • With the paths removed, it reports Pi requires user mode (--user); skipped, as before.

One note: I edited schema/daemon-config.md by hand, because cargo xtask schema in my environment dropped the startup-field rows from the Markdown output. The JSON came out the same as the generator's.

🤖 Generated with Claude Code

Pi keeps its configuration only under the user's ~/.pi/agent, so a system
daemon skipped it and a device needed a second, user-mode daemon just for Pi
while Claude Desktop, Codex and Grok need the system daemon.

A system daemon now manages Pi when daemon.pi.models and daemon.pi.settings
name that user's files (both are required). The plan hands the files, their
state sidecars and any directories it creates to the owner of the nearest
existing ancestor, so Pi can read them and the credential command stays
private to the user. Without daemon.pi paths a system daemon still skips Pi.

ReconcilePlan gains an optional plan-wide owner (Unix), applied after each
write and to created directories; a write is not skipped as already in place
while the file's owner differs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI 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.

🟡 Changes recommended

Privileged filesystem access introduces security vulnerabilities, and ownership recovery and authenticated one-shot validation remain incomplete.

5 open issues
What changed in this PR

This PR lets a system daemon manage Pi for one explicitly configured user, removing the need for a separate user daemon alongside system-only providers.

Changes:

  • Accept paired Pi startup paths and explicitly enable system-mode Pi management.
  • Add ownership handling for planned files, sidecars, and newly created directories.
  • Update startup validation, ownership tests, and configuration documentation.
File Description
schema/​daemon-config.md Document the system-mode Pi opt-in.
schema/​daemon-config.json Update Pi configuration descriptions.
crates/​core/​src/​config.rs Document Pi startup paths and supported daemon modes.
crates/​agent/​src/​reconcile/​plan.rs Add ownership assignment and ownership-aware write checks.
crates/​agent/​src/​reconcile/​mod.rs Enable Pi management when both system-mode paths are supplied.
crates/​agent/​src/​provider/​pi/​mod.rs Infer the target owner and test explicit system-mode management.
crates/​agent/​src/​daemon.rs Accept paired Pi paths and reject incomplete configuration.

Review effort: Balanced

if for_user {
// Pi reads these as the user, so files and any directories the plan
// creates belong to whoever owns the user's Pi directory.
plan.set_owner(owner_of_nearest(models_path));
Comment on lines +503 to +504
std::os::unix::fs::chown(path, Some(owner.uid), Some(owner.gid))
.with_context(|| format!("set owner of {}", path.display()))?;
Comment on lines +237 to +239
(Some(models), Some(settings)) => ResolvedPiStartupConfig {
models: Some(models),
settings: Some(settings),
for (index, change) in self.operations.into_inner().into_iter().enumerate() {
if let Some(contents) = &change.contents
&& already_in_place(&change.path, contents, change.permissions, &observed)
&& owned_by(&change.path, change.owner)
Comment on lines +263 to +267
crate::secure_fs::atomic_write(
&change.path,
&contents,
change.permissions,
)?;

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants