Skip to content

fix(manifest-reload): re-read source TOML on daemon restart so manifest edits take effect without respawning#225

Open
capt-marbles wants to merge 1 commit intoRightNow-AI:mainfrom
capt-marbles:fix/219-manifest-disk-reload
Open

fix(manifest-reload): re-read source TOML on daemon restart so manifest edits take effect without respawning#225
capt-marbles wants to merge 1 commit intoRightNow-AI:mainfrom
capt-marbles:fix/219-manifest-disk-reload

Conversation

@capt-marbles
Copy link

Fixes #219.

Problem

Editing ~/.openfang/hands/{name}/agent.toml and restarting the daemon has no effect — the agent comes back with the old manifest. The only workaround is to delete the agent and respawn it from scratch, losing conversation history, session state, and schedule linkage.

Verified broken in v0.3.2: changed model field from claude-sonnet-4-20250514 to claude-haiku-4-5-20251001 in a hands agent.toml, killed daemon, restarted — API still returned claude-sonnet-4-20250514.

Root Cause

The restore loop in kernel.rs reads manifests exclusively from the SQLite blob:

match kernel.memory.load_all_agents() {
    Ok(agents) => {
        for entry in agents {
            // manifest comes 100% from DB — disk is never consulted
            let mut restored_entry = entry;
            ...
        }
    }
}

AgentEntry had no source_toml_path field, so even if the code wanted to check disk, it had no path to read. load_all_agents in structured.rs read the msgpack blob from the manifest column and returned it directly, no file I/O.

Fix

Three files, one concept: record where each manifest came from, and re-read it on restart.

crates/openfang-types/src/agent.rs

Add source_toml_path: Option<PathBuf> to AgentEntry:

/// Path to the on-disk TOML this agent was originally spawned from, if any.
/// When set, the daemon re-reads this file on every restart and uses it as
/// the authoritative manifest source, so that edits take effect without
/// deleting and respawning the agent.
#[serde(default)]
pub source_toml_path: Option<std::path::PathBuf>,

#[serde(default)] ensures existing DB rows (no column) deserialise to None — no migration required for the msgpack blob.

crates/openfang-memory/src/structured.rs

  • save_agent: inline ALTER TABLE agents ADD COLUMN source_toml_path TEXT DEFAULT NULL (same pattern as session_id). Include the column in the INSERT ... ON CONFLICT UPDATE.
  • load_agent and load_all_agents: read the column (col index 7), pass through to AgentEntry. Falls back gracefully on older DBs that don't have the column yet.

crates/openfang-kernel/src/kernel.rs

Restore loop — after loading from DB, re-read from disk if a source path is recorded:

if let Some(ref path) = restored_entry.source_toml_path.clone() {
    if path.exists() {
        match fs::read_to_string(path).and_then(|s| toml::from_str(&s)) {
            Ok(fresh_manifest) => {
                info!(agent = %name, path = %path.display(), "Reloaded manifest from disk");
                restored_entry.manifest = fresh_manifest;
                // Write refreshed blob back so unchanged files cost nothing next boot
                let _ = kernel.memory.save_agent(&restored_entry);
            }
            Err(e) => warn!("Disk TOML error — keeping DB manifest: {e}"),
        }
    }
}

Parse or read errors fall back to the DB blob with a WARN log — a typo in a TOML file never prevents an agent from starting.

activate_hand — after spawning the agent, write the manifest to ~/.openfang/hands/{name}/agent.toml (only if the file doesn't already exist, to preserve user edits), then record the path:

let hands_toml_path = self.config.home_dir.join("hands").join(&manifest.name).join("agent.toml");
if !hands_toml_path.exists() {
    if let Ok(toml_str) = toml::to_string_pretty(&manifest) {
        let _ = fs::write(&hands_toml_path, toml_str);
    }
}
if let Ok(Some(mut entry)) = self.memory.load_agent(agent_id) {
    entry.source_toml_path = Some(hands_toml_path);
    let _ = self.memory.save_agent(&entry);
}

Behaviour after this fix

Scenario Before After
Edit hands TOML, restart daemon Ignored Picked up
Hands TOML has parse error, restart N/A Warn + use DB blob
Hands TOML deleted, restart N/A Warn + use DB blob
API-only agent (no source path), restart Uses DB blob Uses DB blob (unchanged)
First hand activation No disk file written Writes agent.toml to hands dir

Tests

All 279 existing tests pass. Build is clean.

…st edits take effect without respawning

Fixes RightNow-AI#219.

When a daemon restarts, the restore loop exclusively reads agent manifests
from the SQLite blob — edits to the on-disk TOML (e.g. in ~/.openfang/hands/)
were silently ignored, requiring a full delete-and-respawn to pick up changes.

Root cause: `AgentEntry` had no record of where its manifest came from, so the
restore loop had no path to re-read. The `load_all_agents` query only pulled
the msgpack blob.

**Changes:**

`crates/openfang-types/src/agent.rs`
- Add `source_toml_path: Option<PathBuf>` to `AgentEntry` with `#[serde(default)]`
  for zero-cost backwards compatibility with existing DB rows.

`crates/openfang-memory/src/structured.rs`
- Add `source_toml_path TEXT DEFAULT NULL` column migration in `save_agent`
  (same inline-ALTER pattern already used for `session_id`).
- Read and populate the column in both `load_agent` and `load_all_agents`.

`crates/openfang-kernel/src/kernel.rs`
- Restore loop: if `source_toml_path` is set and the file exists, re-parse the
  TOML and use it as the manifest. On success, the refreshed manifest is written
  back to the DB so unchanged files incur no extra I/O on subsequent restarts.
  Parse or read errors fall back to the DB blob with a WARN log — a bad edit
  never prevents an agent from starting.
- `activate_hand`: after spawning the agent, write the manifest to
  `~/.openfang/hands/{name}/agent.toml` (skipped if the file already exists to
  preserve user edits), then record the path in the `AgentEntry` via a
  `load_agent` + `save_agent` round-trip.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

Title: Agent manifest changes in TOML are silently ignored on daemon restart (loaded from DB instead)

1 participant