镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

feat(agents): start agents from relay-synced config copies - #1185

Merged
wpfleger96 merged 11 commits into
mainfrom
hayt/agent-config-store
Oct 16, 2026
Merged

wpfleger96 merged 11 commits into
mainfrom
hayt/agent-config-store

Conversation

@wpfleger96

@wpfleger96 wpfleger96 commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

Relay-synced agent config, up to the point where agents start from it. Every agent run now goes through a start resolver that reads only this device's confirmed copies. In production every agent is still local (no config channel binding exists until PR C), so there is no visible change yet.

  • Config store (src/features/agents/agent-config.ts): keeps each agent's last confirmed instructions and settings artifacts locally, with a state machine (present, invalid, deleted, unavailable) and a generation/sequence fence, so nothing from a retired binding can overwrite a newer answer. Stored heads are re-verified on load. The settings allowlist checks own keys only, so constructor, toString and __proto__ are unknown settings.
  • Artifact client (src/features/agents/agent-config-artifacts.ts): deterministic d (UUIDv5 of the type in the channel's namespace), current-heads and head-by-d reads that name the config channel in #h (the relay leaves system channels out of reads that don't name them, NIP-CL), per-revision validation, and accepted-vs-current handling for publishes. Two heads for one d decide nothing. Refusals are classified from the relay's response body; transport errors and 5xx stay uncertain.
  • Relay plumbing: a relay-only, verified snapshotRead in src/features/relay/session.ts backs the client's reads. Kind 45010 artifacts publish through a dedicated configArtifacts writer in native.ts that skips the native writer's 15-minute exact-byte retry block, and sign under a bounded lane in src-tauri/src/relay.rs (the relay's default 512 KiB frame less the ["EVENT",…] wrapper, measured on the complete signed event).
  • Synced settings (src/bundled/{claude-code,codex,goose}/settings.ts): per-plugin allowlists. Claude Code and Codex sync model, effort, scope; Goose syncs model, provider, scope. Instructions are their own artifact. The agent folder (workspace) is device-local and never synced. Each plugin now exposes its schema and merge on AgentType.syncedSettings.
  • Start resolver (src/features/agents/agent-start.ts): resolveStart builds a run's config from the local config plus any synced copies the binding names, and returns the revision IDs it used, or a refusal (invalid, deleted, lost access) with a reason. It makes no relay request. The runner calls it for every delivery, passes config and revisions to the runtime, records them in Runs, and shows a refusal as the mention's error without launching.
  • Binding input: AgentsService takes an optional configBinding(agent) returning { settings?: { mode, subject }, instructions?: { mode, subject } }. Production leaves it unset; PR C fills it from the config channel binding.
  • Launch boundary tests (src/features/agents/launch-boundary.test.ts): mentions go through the real service, runner, resolver and Claude Code, Codex and Goose runtimes from signed present heads, on a real createRelaySession over a transport that records every request. They check the model, instructions and workspace each runtime actually used and the revision IDs on each delivery. During a start the only request allowed is each Codex mention's own conversation read, compared filter for filter (its channel, the mention by ids and its thread by #e, kinds 9 and 40002, until the mention, limits 1 and 50) and counted; any other request, or a wider or different one, fails the test, online or with the relay unreachable. Offline, Claude Code still receives the synced prompt, and Codex's second mention ends with the read error on that mention and no second turn/start. With the relay back, a real config read through readHead shows up on the same counter. A deleted settings head is attempted once: nothing launches, its reason is on the mention, and nothing retries after the service's next 5-second tick.
  • Types without synced settings: a type with no syncedSettings merge refuses to start only when its settings are synced (explicit synced mode, or this device has had a synced revision), using the resolver's own isSynced check; local and migrating agents with no synced revision start from their local config.

Not in this PR: reading the relay's NIP-11 max_message_length, and the browser broker's 64 KiB cap on kind 45010.

Constraints PR C must meet before using this:

  • Teams: starts still pass the legacy local team list (host.team). PR C must format the resolver's admitted teams and stop injecting the legacy list after migration.
  • Run lifetimes: Runs records each agent's latest delivery, not every live session; a runtime can return while workingUntil work continues. Before "Restart to apply", PR C must track actual session and run lifetimes and not clear older staleness just because another conversation starts.
  • New-session config adoption: Claude Code and Goose reuse an existing runtime entry and its entry.config, so passing resolved config on a delivery does not yet change an existing entry. PR C must prove new sessions use the resolved config while existing work keeps its snapshot.

Related: #1155

@wpfleger96

Copy link
Copy Markdown
Member Author

Step-5 source review — first pass

Reviewed commit 4bd4f7b38cfddbd6e257c42bd379f492b4e7450e, confirmed against the remote hayt/agent-config-store ref. Keep the PR draft: one IMPORTANT test-contract gap remains. This is a source assessment, not native/live acceptance.

IMPORTANT — launch tests do not exercise the resolver/transport seam

Correctness: src/bundled/claude-code/runtime.test.ts:165–191,1576–1614 constructs a delivery with DEFAULT_CONFIG and calls runtime.run directly. src/bundled/codex/runtime.test.ts:355–382,3078–3146 does the same with a literal config. Both count mocked runtime-facing methods, not transport requests below the production runner/resolver. The new src/features/agents/service.test.ts:1487–1497 exercises the service/runner but ends at an Echo mock and asserts only local config and [] revisions. It does not connect those two sets of tests.

Consequently the request-count tests do not protect the newly added production call to resolveStart, contrary to the launch-wiring report. They also do not establish plan T7's responsive-relay positive control during sync, offline repeat, or Goose coverage. A resolver that adds a fetch, or wiring that ignores its resolved config/revisions, is outside their observation boundary.

Required correction: add a launch-boundary test that drives the real runner → resolveStart → runtime path with seeded store/core/profile caches and observes the shared transport counter. Assert the config actually used and non-empty revision IDs in a synced fixture, demonstrate the same counter sees sync requests, repeat with transport unavailable, and cover Claude launch, Codex thread/turn and Goose as T7 requires. Include a refused resolver result proving no runtime launch, its owner-visible reason, and no immediate retry. Keep existing runtime unit tests, but describe their evidence as runtime method-call checks rather than transport-level coverage. Fixture-only synced inputs do not require activating production sync in PR A.

Scope judgments and PR C conditions

  • Local teams: retaining host.team is correct for PR A's no-visible-change split. service.ts:919–930 supplies neither config modes nor synced teams, so the resolver returns the original local config with empty revisions. PR C must format the resolver's admitted teams and stop also injecting the legacy list after migration.
  • Revisions: runner.ts:1641–1653 really resolves before delivery and attaches its config/revisions. However, Runs is only a latest-delivery record, not a record of every live session. A runtime can return while workingUntil work remains (claude-code/runtime.ts:420–424; codex/runtime.ts:740–767). A later delivery replaces the agent's revision set and clears stale (agent-start.ts:158–160) while older work remains. That does not mutate the older delivery, and is harmless while every production set is empty and no changed consumer exists. Before PR C uses it for “Restart to apply,” track actual session/run lifetimes and do not clear older staleness merely because another conversation starts.
  • Runtime config adoption: Claude and Goose entry(pubkey, initial) return the existing entry unchanged (claude-code/runtime.ts:582–584; goose/runtime.ts:468–470). Their launch closures read entry.config, currently updated from local rows by sync. Passing resolved config through a delivery is therefore not yet sufficient to activate synced config for an existing runtime entry. Preserve PR A's local behavior; PR C must prove new sessions use resolved config while existing work retains its intended snapshot.
  • Blocked start: the thrown reason reaches working.release and LocalWork.error (runner.ts:1681–1684,1753–1772), then the message's activity accessory via requestWork/localWorkLabel. It is local owner-visible activity, not a channel error post. The queue shifts the job before resolution (runner.ts:1599–1603) and does not requeue in the catch. Timers advance their occurrence before resolution. Thus no immediate same-job retry loop is present; future triggers are separate attempts. A resolver-specific regression is still missing.

The stable writer/signer, separate fresh snapshot reader, and profile-cache traces support the implementation choices. The store's present state remains a caller admission contract: PR C must validate every installer against the selected channel/type/identity.

Evidence limits

At inspection, this head reports successful DCO, Semgrep and zizmor checks. The latest broad CI run returned for this branch is on fd9bd6437, not the reviewed head. No local suites were rerun to duplicate CI; no native/live probe was performed in this review. Current-head broad validation and independent native/live evidence remain separate gates. Also update the PR description: it still says nothing calls this code and lists step 5 as future work.

@wpfleger96 wpfleger96 changed the title feat(agents): add dormant agent config store and settings split feat(agents): start agents from relay-synced config copies Oct 15, 2026
@wpfleger96

Copy link
Copy Markdown
Member Author

Step-5 source review — pass 2 of 3

Pinned to bfbfae4ab52dee03992e5f0801c03d55496c89e2, verified against origin's hayt/agent-config-store ref. Keep draft. The first blocker is partially fixed, not closed.

The new tests genuinely connect AgentsService → runner → resolveStart → each bundled runtime. They assert resolved model/instructions/workspace and non-empty revision IDs. The plugin-owned schema/merge and the small Goose run-part extraction follow the approved design. Production still leaves the binding unset. These are substantive improvements over the first pass.

IMPORTANT — Correctness: the request evidence is still narrower than T7

src/features/agents/launch-boundary.test.ts:84–122 records calls into a fake session, not requests below a real session. It then discards every path outside REQUESTS. Even within that set, opaque calls such as profiles.ensure(pubkeys) and agentMemories.read(pubkey) have no kinds argument and cannot match the config-kind regex. The profile implementation is a no-op, not the real cache/read path (:146–153). Thus an extra start-time profile/memory read, or a config read through another recorded method, can pass the zero-config assertion. The offline flag only fails those five methods, not every network operation.

The positive control calls the wrapped mock snapshotRead directly (:235–244); it checks the wrapper/regex against a hand-written argument, not whether production sync and launch share an observed request boundary. T7 requires a counter that sees sync and distinguishes the turn's permitted message-context requests from all other start requests (plan §4.3 and T7: PLANS/BUZZ_APP_AGENT_CONFIG_SYSTEM_CHANNELS_PLAN.md:191–208,351). Only checking the five config kinds is weaker than that contract.

Correction: preferably compose the real relay session over a counted fake ReadTransport, seed/settle its caches before taking the baseline, and drive the positive control through the real artifact reader (readCurrent/readHead are available in this branch; no need to import PR C). Assert every request in the launch window: none except explicitly classified Codex context reads. If retaining a session-level test as supplemental evidence, inspect config kinds on every recorded call, account explicitly for opaque fetch-capable methods, and label it session-method evidence rather than transport coverage. That alone is not the requested transport proof.

IMPORTANT — Correctness: offline and no-retry assertions lack the boundaries they claim

Codex offline behavior is permitted; the current assertion is incomplete. A context-read failure is not a config fallback failure: §4.3 explicitly allows those reads. Starting thread/start from the stored copy while an uncached conversation prevents the turn is compatible with that split. But :386–398 stops observing at thread/start, before runtime.ts:1079–1096 performs history/core reads and starts the turn. It neither asserts the documented failure nor observes the whole attempt. Its positive control then restores connectivity. Whether subsequent work runs offline is left to scheduling. Claude's offline branch likewise stops at process creation without awaiting its synced prompt (:275–283).

Correction: leave the failing transport in place until an observable terminal outcome for the second mention; assert the context-only failing requests, the activity error, no second turn/start, the resolved config/revisions, and no forbidden request through that outcome. Alternatively, explicitly hold the allowed context read while checking the completed config phase, then settle it and check the final outcome. Reconnect only after this boundary. A successful offline Codex model turn is not required when its permitted context read cannot succeed.

Refusal proves no runtime launch and a local reason, but not no retry. :470–481 observes the reason and triggering message ID, with no delivery or Goose process. However, repeated resolver refusals would still produce no delivery/process and zero config requests. A longer wait cannot repair that missing observation. The fixed 200 ms is also shorter than the service's 5,000 ms tick (service.ts:671,1041–1046).

Correction: count calls to the binding/resolver for this trigger, await the refusal/drain outcome, advance a controlled clock through the next service tick, and assert exactly one attempt with the triggering message's structured state: "error" and expected reason. Dispose the fixture service/context and runtimes in cleanup so background work cannot escape the observation window. This follows the repo's deterministic-test rule: negative assertions need a completion barrier, and retry timing uses a controlled clock.

Source reasoning still finds no retry bug in the runner: the job is shifted before resolution (runner.ts:1599–1603), and the catch does not requeue (:1681–1707). This is a regression-evidence defect, not a claim that production is retrying.

IMPORTANT — Correctness: no-merge refusal rejects valid local/migrating settings

service.ts:945–950 rejects any supplied settings binding when the type lacks syncedSettings, including mode: "local" and "migrating" with no prior revision. The new ConfigBinding API accepts those modes. They must preserve the exact local config, and do not need a schema or merge (agent-start.ts:15,74–84; plan §4.1). An otherwise runnable type without this optional capability now fails before the resolver can preserve local behavior.

Correction: require a merge only when settings are effectively synced: settings.mode === "synced" || configStore.hadVersion(settings.subject). The had-version half matters: do not allow fallback just because a binding says local/migrating after this device has had a revision. Without a merge, omit unsynced settings from the resolver input. Cover local/migrating with no prior revision (unchanged launch), explicit synced mode (refusal), and a prior revision despite local/migrating mode (refusal). This is introduced by the follow-up; production's currently unset binding limits exposure but does not make the new API's contract correct.

PR description and evidence limits

The rewritten description now matches the architecture and correctly says production remains local, names the binding input, and lists the NIP-11 and broker-cap omissions. Two precision corrections remain: the positive control is presently a direct mock call, not a real sync read; the 15-minute age/retry block belongs to the native writer, not the outbox itself (native.ts:473–475,861–865). Runs remains a latest-delivery revision record, not completed live-session tracking; retain the three previously agreed PR C constraints on teams, run lifetimes and existing runtime entries. These wording corrections are MINOR; the test/API defects above block handoff.

Scores: Minimalism 9/10 — the approved narrow binding and plugin-owned schema avoid saved test-only state; Elegance 9/10 — the shared resolver/run parts preserve ownership; Correctness 8/10 — fix the observation/async gaps and the effective-mode guard above.

At this head DCO, Semgrep and zizmor report success. The latest broad CI returned for the branch is still the successful run on fd9bd6437, not this head; broad current-head validation is unestablished, not confirmed running by the metadata I inspected. I ran no local suites, builds or live probes. This is the independent source assessment; native/live verification and the agreed PR C shipping gates remain separate. One review pass remains after these corrections.

@wpfleger96

wpfleger96 commented Oct 16, 2026 •

Copy link
Copy Markdown
Member Author

Final source review — pass 3 of 3

Pinned to adc59d63760b3f8fca2b556dc96fcc2bc74e2893, verified against origin’s hayt/agent-config-store. Keep draft and escalate the remaining blocker to Will; do not start a fourth implementation/review pass.

Disposition of the four requested corrections

  1. Real request boundary: substantially fixed, but not closed. The fixture now composes a real createRelaySession with a transport recording both query and encrypted-memory requests. The positive control executes the real artifact readHead through snapshotRead. The remaining defect is the conversation allowlist below.
  2. Offline completion: fixed for the requested boundaries. Codex remains offline until the exact second mention reaches error, checks Relay unreachable, two resolved thread/start calls but only one turn/start, and examines the recorded requests before restoring the relay (launch-boundary.test.ts:499–520). Claude now waits for synced instructions in the second process’s initialization input, not merely process creation (:368–393). This establishes prompt delivery, not completion of a Claude model turn; the latter was not the requested boundary.
  3. No-merge effective mode: fixed. The exported isSynced is shared with the resolver; the service refuses only explicit synced mode or an existing had-version marker. The five new service cases cover local/migrating without history, explicit synced, and local/migrating with history (agent-start.ts:69–87; service.ts:941–967; service.test.ts:1155–1244).
  4. No retry: fixed for the next-service-tick contract. The deleted-head test counts binding calls, waits for the exact mention’s terminal error, advances controlled time by 6 seconds, and asserts one attempt, one activity row, no delivery and no Goose launch. Fixtures are disposed (launch-boundary.test.ts:145–149,580–609). Source confirms the job is shifted before resolution and is not requeued by the error path (runner.ts:1599–1603,1641–1642,1681–1707). This is not a proof against an arbitrary retry scheduled after the observation window.

IMPORTANT — Correctness: the allowed reads are classified by kind, not conversation filter

launch-boundary.test.ts:93–102 accepts any nonempty set of filters whose kinds are drawn from 9 and 40002. It does not check the triggering channel, root/message IDs, time cutoff, limits, filter count, or unexpected keys. Both the online allowlist and the offline “only conversation reads failed” assertion use that same predicate (:302–308,514–519).

For example, an additional [{ kinds: [9], limit: 10000 }] read, or a read naming an unrelated channel, would be accepted as conversation traffic. Thus the test still cannot distinguish the specifically permitted context reads from an unrelated start-time message fetch. This is the exact-filter requirement in Paul’s final-pass assignment, not a request to widen production scope.

Smallest correction: derive the expected requests from the triggering mention and assert their complete filter arrays, including cardinality. For these empty-history, thread-scoped fixtures, the production builder emits the root-ID and #e filters with kinds [9, 40002], the triggering #h, until: mention.created_at, and limits 1/50 (src/bundled/codex/history.ts:12–25). Reject extra requests/filters. Mutation-check a same-kind extra read or wrong-channel filter, not just a kind-45010 insertion. Do not add other conversation kinds merely to make the assertion pass.

Answers to the additional review questions

  • Post-run core refresh is legitimate, with a bounded claim. working.release calls work.finish, then host.finished only after all work holds settle (runner.ts:1753–1772); the service’s finished callback initiates the cache refresh (service.ts:1012–1014). mentions increments before delivery, so in these separate top-level conversations an awaited core read during either start sees unfinished work and cannot use the exception. The allowed agent/slug are exact. This is an outcome-time gate, not a provenance check: it does not establish that every arbitrarily delayed fire-and-forget memory request originated from finished. I found no such production start request in the traced paths. The offline request summary should explicitly exclude this legitimate turn-end refresh rather than claim every failed request is conversation traffic.
  • Warming profiles and core is consistent with T7. Plan §4.3 explicitly starts counting after the input caches are seeded, and separately permits the initial bounded core read. These tests do not establish cold-first-start behavior, and should not be described as doing so.
  • MINOR — timing-sensitive setup remains. await channels.ensureList() does not await discovery: its implementation returns void and launches discover() (store.ts:2057–2060). Seeing the agent in the service is not discovery/listing completion. The fixed 50 ms sleep at launch-boundary.test.ts:260 therefore remains a timing assumption before clearing the counter. The awaited profile/core warmups are real barriers for those inputs, but do not explicitly join every independent setup task. Replace the sleep with observable channel-discovery and service-listing completion before taking the baseline. This is a plausible source of spurious forbidden-start traffic under load, not an identified cause of the unrecorded hook failure.

Evidence and description

Duncan supplied terminal excerpts and mutation commands in the thread’s mutation-evidence message (875fa3b353ef05a1a9239cdc60b26c1b07742fa4058e94e52affa5e6ea039fba). Treat those as author-reported execution evidence, not independently reproduced results: delayed requeue fails with the 6-second advance and passes with 10 ms; swallowing Codex history failure fails; removing synced instructions fails the Claude test; inserting a config read fails all four boundary tests. The Claude mutation does not independently isolate the offline prompt-wait assertion. No raw logs or per-mutation restored GREEN runs were retained; the reported full agents run and hook are the restoration evidence.

The description now correctly names the real readHead positive control and the native writer’s 15-minute block, and retains all three PR C constraints: resolved teams replacing legacy injection, actual run/session lifetime tracking, and adoption of resolved config by new runtime sessions. Its absolute request wording should be tightened to the explicitly checked conversation filters and the post-work refresh exception.

Hosted evidence inspected: DCO, Semgrep and zizmor succeeded at this head. The newest broad CI run returned for this branch is still the successful run at fd9bd6437, not the reviewed head; broad CI at adc59d637 is unestablished, not confirmed running. Duncan reports a successful full pre-push hook with 6304 passed / 6 skipped. I ran no local test suites, builds, or live probes, and made no repository edits. This source review does not replace the separate native/live shipping checks.

The shared guard and real-session fixture are structurally sound; the remaining agreed-contract test defect prevents a correctness pass. The three-pass budget is exhausted.

@wpfleger96
wpfleger96 force-pushed the hayt/agent-config-store branch from adc59d6 to 49f95f4 Compare October 16, 2026 00:18
@wpfleger96

Copy link
Copy Markdown
Member Author

Scoped follow-up review — 49f95f48ffd54d9f61e787ccb6ff931ef94e99a2

Pass for the additional round Will authorized. No CRITICAL or IMPORTANT findings in the conflict resolutions or final test commit. This closes the remaining exact-conversation-read finding from my previous review. This is not a new whole-PR review or a declaration that pending CI has passed.

Origin's hayt/agent-config-store and GitHub both identified this exact head. I compared the old series ending at adc59d637 with the rebased series on 922643911, then traced the changed integration points at the pinned ref.

Conflict resolution

  • No resolver bypass in the delivery paths added by concurrent Interests. drain selects mentions, queued Interest work and due timers, and dispatches all through the same private start. That method calls host.start(agent), refuses before dispatch on failure, and attaches that result's config and revisions to the delivery. Both ordinary run and standalone runOnce receive that same delivery. The resolver call occurs per delivery, not once per drain or agent. See runner.ts:1647–1799.
  • The other resolutions preserve both sides. The service differences are the existing config-store integration plus main's TIMEOUT_LIMIT_MS import. The session keeps main's name search, directory-read retry and richer verified reader alongside this branch's separate relayRead/snapshotRead; the config read was not substituted for main's reader.
  • The per-agent Runs limitation is acceptable only while dormant. A later delivery overwrites the revisions and clears staleness for the entire agent (agent-start.ts:166–168). Concurrent Interest work makes overlapping deliveries more common, so this would be incorrect for a live “Restart to apply” indicator. At this head, however, the production constructor supplies no config binding (app/services.ts:76–83), and my search of src/ found no production calls to Runs.changed or consumers of restartToApply/the stored revision snapshot. The bookkeeping cannot currently clear a user-visible mark or alter another delivery's config. PR C must track actual live sessions/runs and preserve older staleness before exposing or acting on it; the PR description retains that explicit constraint. This is not approval to activate the current tracker unchanged.

Final test commit

  • The remaining finding is fixed. launch-boundary.test.ts:93–101, 307–318 now compares the complete ordered request list, including exact channel, kinds, mention/thread IDs, cutoff and limits. It rejects missing or extra requests as well as broader/wrong-channel filters. These expected filters match the actual Codex history read after the relay reader's set normalization. The empty-history fixture legitimately has one request containing two filters. The existing turn-end core-refresh exception remains separate.
  • The replacement wait is reliable for this fixture. The fixture has one roster, no restored startup state and no discovery pagination. applyDiscovery applies the complete roster and synchronously performs access invalidation/cache clearing before publishing the ready channel list (store.ts:945–992; session.ts:379–432). The subsequent profile/core warming therefore starts after the cancellation boundary. Metadata may still finish afterward, but for this unchanged admitted roster it does not change access and repeat the cancellation. This is an observable lifecycle barrier, not a longer timing allowance; it is not a general assertion that every discovery task has finished.
  • Duncan's supplied RED/GREEN excerpts and old-check controls support detection of the extra same-kind limit-500 request and wrong-channel request. The reader rejects limits above 500 before transport dispatch (reader.ts:233–238), so 500 is the relevant valid broad-read mutation. I did not independently rerun these mutations or suites.

Quality assessment: the conflict resolution preserves the single delivery owner; the assertion directly expresses the required contract; the setup change removes timing dependence without adding production machinery. No material minimalism, elegance or correctness defect found within this scope.

Validation limits: source review only; no repository edits, local suites or live probes. At my single CI inspection on this head, DCO, zizmor, Native browser fixture and Flake check selection passed; broad CI was still running. The full pre-push result of 6344 passed / 6 skipped is author-reported. Paul still needs the final CI result before handoff.

@wpfleger96
wpfleger96 marked this pull request as ready for review October 16, 2026 14:59
@wpfleger96
wpfleger96 requested a review from a team as a code owner October 16, 2026 14:59
Hayt and others added 11 commits October 16, 2026 11:01
…tings split

Groundwork for relay-synced agent config (PR A steps 1-4): a local store with a fenced state machine, a NIP-AR artifact client, a bounded sign lane for kind 45010, and per-plugin synced-settings allowlists. Nothing is wired in yet.

Signed-off-by: Hayt <211b96e6a2b7f45fd4047988976c7bbbeeda0c15f3ae7b32eec20834b5a55118@buzz.block.builderlab.xyz>
…unds

The relay rejects consistency on artifact queries, the HTTP seam dropped typed refusal reasons, decoded NUL and foreign team ids passed validation, live applies lacked a generation fence, the signing ceiling ignored preserved tags, and prototype keys passed the settings allowlist.

Signed-off-by: Hayt <211b96e6a2b7f45fd4047988976c7bbbeeda0c15f3ae7b32eec20834b5a55118@buzz.block.builderlab.xyz>
…mmand

The size regression called the signer directly, so it could not catch
relay_sign choosing the ordinary signer or dropping the full-event check.

Signed-off-by: Hayt <211b96e6a2b7f45fd4047988976c7bbbeeda0c15f3ae7b32eec20834b5a55118@buzz.block.builderlab.xyz>
The relay leaves system channels out of any read whose #h does not name
them (NIP-CL), members included, so a #d-only head read of a config
channel returned nothing. The test fixture now serves nothing to such a
filter.

Signed-off-by: Hayt <211b96e6a2b7f45fd4047988976c7bbbeeda0c15f3ae7b32eec20834b5a55118@buzz.block.builderlab.xyz>
NIP-AP settings must not carry machine-local paths, so each device keeps its own workspace in the local row. Remote settings naming workspace are now invalid as an unknown key.

Signed-off-by: Hayt <211b96e6a2b7f45fd4047988976c7bbbeeda0c15f3ae7b32eec20834b5a55118@buzz.block.builderlab.xyz>
Adds the synchronous start resolver over the synced store (local,
migrating and synced modes, team sections, had-version and the offline
rule), the relay-only snapshot reader and native artifact writer on the
session, run revision tracking for restart-to-apply, and zero-request
start tests for Claude Code and Codex.

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
Each run's config and synced revisions now come from resolveStart over this device's config store; with no config channel yet every type is local, so launches are unchanged.

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
…d runtimes

Agent types now carry their synced-settings schema and merge, and AgentsService takes an optional config binding that PR C fills from the config channel. launch-boundary.test.ts drives Claude Code, Codex and Goose mentions end to end from present heads, counts relay-session requests online and offline, and checks that a deleted head refuses with its reason on the mention.

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
The refusal fired for any settings binding, so a local or migrating agent of a type without syncedSettings could not start. It now uses the resolver's own synced test (explicit synced mode, or a synced revision this device has had), exported as isSynced.

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
…d attempt

Runs the start boundary on a real relay session over a counting transport, allows only Codex conversation reads, keeps Codex offline through its failed mention, and advances a fake clock past the service tick to show a deleted head is attempted once.

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
…l discovery

A kind-only check let a wider or wrong-channel read pass as conversation traffic; each mention's read is now compared filter for filter, and counted. Setup waits for the channel list to be ready, whose access change would otherwise cancel later setup reads, instead of sleeping 50 ms.

Signed-off-by: Duncan <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@buzz.block.builderlab.xyz>
@wpfleger96
wpfleger96 force-pushed the hayt/agent-config-store branch from 49f95f4 to a11650c Compare October 16, 2026 15:02

@kalvinnchau kalvinnchau left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 Approved with non-blocking P3 follow-ups at a11650c. No normal-path launch regression or actionable security defect was established. The inline comments cover storage recovery, launch-test evidence, and optional refusal-policy cleanup. Production config bindings remain deferred. Typecheck, changed-file lint, and diff checks passed; full Vitest validation was not green (timeouts/incomplete runs). Chromium and WebKit fixture-backed app journeys passed, but live staging validation was blocked by the no-Keychain constraint; native signing and actual harness launches remain unverified.

}
if (value?.version !== 1 || typeof value.entries !== "object") return empty;
const entries: Record<string, Entry> = {};
for (const [k, entry] of Object.entries(value.entries)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 [P3] Validate persisted entry shapes before iterating. typeof null === "object", so {"version":1,"entries":null} reaches Object.entries(null), while {"version":1,"entries":{"k":null}} throws at entry.head. These cases were reproduced in review. Since this store is constructed in AgentsService, malformed storage can prevent service construction. Please reject null/non-record containers and malformed entries, including unknown states and invalid generation/sequence values. Non-blocking here: production does not yet write this key, so the demonstrated trigger requires corrupted or manually edited storage.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

👍 will address in a downstack PR

run: (delivery) => {
deliveries.push(delivery);
return real.run?.(delivery);
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 [P3] Include production runtime synchronization or narrow the evidence claim. This fixture registers run handlers, but the Claude Code and Goose plugins also subscribe runtime.sync(running), which creates entries from local agent.config before delivery. Their entry lookup then reuses those entries, so the fixture overstates adoption of resolved config even for new sessions. Please wire sync into the fixture and mark synced adoption pending #1226, or explicitly narrow the test/documentation claim. This is a statically traced coverage gap, not a reproduced current launch regression; production bindings remain deferred.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

👍 will address in a downstack PR

const merge = this.types().find(
(type) => type.key === agent.type,
)?.syncedSettings;
if (!merge && isSynced(this.configStore, settings))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 [P3] Optional maintenance follow-up: keep missing-merge refusal in the resolver with the other refusal rules. This callback repeats the runner's type lookup and handles one refusal outside resolveStart, then casts withSynced below. Passing the optional synced-settings capability into the resolver would give this policy one owner. No runtime failure was established; this is non-blocking cleanup.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

👍 will address in a downstack PR

@wesbillman wesbillman left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 Star Lord’s automated source review via Wes’s account.

One P2 finding is detailed inline: a synced Codex effort value that this device cannot support passes validation and is silently replaced by the model default. This affects the new bound/synced launch path; production agents without config bindings continue to use the existing local path. Source assessment: 8/10 pending that explicit-error contract fix.

Reviewed head a11650c against base 85ebd91. Inspected the config store/resolver, relay snapshot and artifact publication boundaries, native signing limits, harness integration, regression-test source, public PR description (no attached images), and commit messages. The documented follow-up requirements for team integration, run lifetimes, new-session adoption, and browser/advertised size limits remain outside this foundation’s completed scope.

Source-only review: no PR code, tests, builds, or app were executed; current CI, native/live behavior, performance, error/retry focus, and human acceptance were not verified. This COMMENT review is non-blocking and is not approval.


export const syncedSettingsSchema = {
model: "string",
effort: "string",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 P2 — Reject unsupported synced effort instead of silently defaulting. This schema accepts every string, so a confirmed synced settings head with a nonempty effort unsupported by this device’s selected model passes resolveStart and reaches CodexRuntime.prepare. There, src/bundled/codex/runtime.ts:971–976 discards unsupported settings.effort and substitutes selected.defaultReasoningEffort. For example, a synced xhigh setting with a local model catalog offering only low/high starts at the default while the delivery records that synced revision as applied. NIP-AP’s Settings contract requires an explicit error for an unsupported setting, rather than a silent default. Keep the existing local/per-turn fallback behavior where intended, but validate an explicit synced effort against the effective model’s supported levels before starting the turn (using the model data the runtime already loads), and add a launch-boundary case that asserts refusal/no turn for an unsupported synced effort. This concerns the new synced-binding path; the production local path is currently unbound.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

👍 will address in a downstack PR

@wpfleger96
wpfleger96 merged commit b4f484d into main Oct 16, 2026
32 checks passed
@wpfleger96
wpfleger96 deleted the hayt/agent-config-store branch October 16, 2026 15:55
tellaho added a commit that referenced this pull request Oct 16, 2026
…ui-polish

* buzz-app/main: (43 commits)
  Unify file attachment cards and file-type artwork (#1220)
  feat(agents): start agents from relay-synced config copies (#1185)
  render shared sessions as attachment cards (#1217)
  fix(channels): keep other channels' permission reads alive across a delete (#1245)
  feat(channels): mark temporary channels in the sidebar (#1249)
  test(browser): fix six intermittent browser test failures and a composer caret bug (#1239)
  Close channel tabs together and make tab shortcuts predictable (#1154)
  Make message retry controls compact and accessible (#1221)
  fix(messages): end deferred composer focus on any focus move; stabilise hover-preview probe (#1244)
  fix(messages): keep restored-channel focus off the composer (#1243)
  fix(me): record reads in Solo chats opened from the sidebar (#1183)
  feat(inbox): archive or restore the open conversation with Control-Command-A (#1181)
  fix: keep panel divider dragging responsive (#1067)
  feat(agents): find people for agents through the relay's name-only profile search (#1124)
  fix(agents): explain why Create agent is disabled (#1232)
  feat(agents): run up to 8 Interests at once for every agent type (#1100)
  fix(codex): set up Codex from the agent creation card, as Claude does (#1231)
  Shrink the thread loading mark by a third (#1214)
  feat(agents): plugins add tools to every agent session (#1216)
  fix(sidebar): re-measure every drop target when a section resizes mid-drag (#1230)
  ...

Signed-off-by: Taylor Ho <taylorkmho@gmail.com>
loganj added a commit that referenced this pull request Oct 16, 2026
Main fails typecheck since #1250 made effort required and #1185 added
this fixture without it. Same one-line fix as in #1257 and #1260.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.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.

3 participants