diff --git a/apps/website/content/AGENTS.md.template b/apps/website/content/AGENTS.md.template index fee728e2d..e82af2492 100644 --- a/apps/website/content/AGENTS.md.template +++ b/apps/website/content/AGENTS.md.template @@ -127,4 +127,4 @@ The private React render entry also accepts an optional `ReactRenderFunctions` h Computed Functions has an experimental local React preview at https://threadplane.ai/docs/render/api/provide-render?frontend=react. Three authored samples preserve native $computed expressions and use RenderSpec.functions for uppercase/reverse text, products and browser-local dates. Only complete expression nodes calculate; numeric/date prefixes never publish provisional results. Heading/Card/Value display resolved props and native children. Pure synchronous callbacks receive owned frozen arguments and may run repeatedly. Play/Pause, seek, Finish and Reset share one bounded private parser owner; sample changes and page exit revoke old callbacks. It makes no backend or model requests. Build with `npx nx build cockpit-render-computed-functions-react` and verify with `npx nx e2e cockpit-render-computed-functions-react`. -The native Skills React preview at https://threadplane.ai/docs/deep-agents/capabilities/skills?frontend=react shares the existing Python Skills graph and enforced read-only /skills/ mount with Angular. Its prompt index contains skill names, descriptions, and instruction paths; bodies and references load on demand. PrivateStateAttr excludes Skills keys from declared input/output schemas, while the pinned graph exposes metadata in root values and checkpoints. Optional load errors can appear when produced and normal loads can omit them. Its named custom visibility event always includes both keys, using an empty-list fallback for load errors. The React preview distinguishes requested paths from successfully inspected tool results; Angular shows requested paths without result validation. Saved index authority and read evidence are separate, and archived evidence cannot authorize Send. Stop or page exit revokes active work. This Skills contract makes no inference about Memory visibility. Build with `npx nx build cockpit-deep-agents-skills-react` and verify with `npx nx e2e cockpit-deep-agents-skills-react`; React remains a private source-build package. +The native Skills React preview at https://threadplane.ai/docs/deep-agents/capabilities/skills?frontend=react shares the existing Python Skills graph and enforced read-only /skills/ mount with Angular. Its prompt index contains skill names, descriptions, and instruction paths; bodies and references load on demand. PrivateStateAttr excludes Skills keys from declared input/output schemas. The local compiled graph exposes metadata in root values and checkpoints; observed deployed API root values omit private Skills keys while saved checkpoints retain metadata, consistent with output-schema filtering. Optional load errors can appear when produced and normal loads can omit them. Its named custom event includes both keys with an empty-list error fallback for Angular's live index. React reads native root values and, when the public stream omits metadata, the confirmed saved checkpoint. The custom event does not supply React index authority or confirmed-read proof. Confirmation permits only absent live skills_metadata/skills_load_errors; every present private value and ordinary value must match the exact checkpoint, and loaded values must agree fully. Saved checkpoint metadata remains authoritative. The React preview distinguishes requested paths from successfully inspected tool results; Angular shows requested paths without result validation. Saved index authority and read evidence are separate, and archived evidence cannot authorize Send. Stop or page exit revokes active work. This Skills contract makes no inference about Memory visibility. Build with `npx nx build cockpit-deep-agents-skills-react` and verify with `npx nx e2e cockpit-deep-agents-skills-react`; React remains a private source-build package. diff --git a/apps/website/content/CLAUDE.md.template b/apps/website/content/CLAUDE.md.template index fee728e2d..e82af2492 100644 --- a/apps/website/content/CLAUDE.md.template +++ b/apps/website/content/CLAUDE.md.template @@ -127,4 +127,4 @@ The private React render entry also accepts an optional `ReactRenderFunctions` h Computed Functions has an experimental local React preview at https://threadplane.ai/docs/render/api/provide-render?frontend=react. Three authored samples preserve native $computed expressions and use RenderSpec.functions for uppercase/reverse text, products and browser-local dates. Only complete expression nodes calculate; numeric/date prefixes never publish provisional results. Heading/Card/Value display resolved props and native children. Pure synchronous callbacks receive owned frozen arguments and may run repeatedly. Play/Pause, seek, Finish and Reset share one bounded private parser owner; sample changes and page exit revoke old callbacks. It makes no backend or model requests. Build with `npx nx build cockpit-render-computed-functions-react` and verify with `npx nx e2e cockpit-render-computed-functions-react`. -The native Skills React preview at https://threadplane.ai/docs/deep-agents/capabilities/skills?frontend=react shares the existing Python Skills graph and enforced read-only /skills/ mount with Angular. Its prompt index contains skill names, descriptions, and instruction paths; bodies and references load on demand. PrivateStateAttr excludes Skills keys from declared input/output schemas, while the pinned graph exposes metadata in root values and checkpoints. Optional load errors can appear when produced and normal loads can omit them. Its named custom visibility event always includes both keys, using an empty-list fallback for load errors. The React preview distinguishes requested paths from successfully inspected tool results; Angular shows requested paths without result validation. Saved index authority and read evidence are separate, and archived evidence cannot authorize Send. Stop or page exit revokes active work. This Skills contract makes no inference about Memory visibility. Build with `npx nx build cockpit-deep-agents-skills-react` and verify with `npx nx e2e cockpit-deep-agents-skills-react`; React remains a private source-build package. +The native Skills React preview at https://threadplane.ai/docs/deep-agents/capabilities/skills?frontend=react shares the existing Python Skills graph and enforced read-only /skills/ mount with Angular. Its prompt index contains skill names, descriptions, and instruction paths; bodies and references load on demand. PrivateStateAttr excludes Skills keys from declared input/output schemas. The local compiled graph exposes metadata in root values and checkpoints; observed deployed API root values omit private Skills keys while saved checkpoints retain metadata, consistent with output-schema filtering. Optional load errors can appear when produced and normal loads can omit them. Its named custom event includes both keys with an empty-list error fallback for Angular's live index. React reads native root values and, when the public stream omits metadata, the confirmed saved checkpoint. The custom event does not supply React index authority or confirmed-read proof. Confirmation permits only absent live skills_metadata/skills_load_errors; every present private value and ordinary value must match the exact checkpoint, and loaded values must agree fully. Saved checkpoint metadata remains authoritative. The React preview distinguishes requested paths from successfully inspected tool results; Angular shows requested paths without result validation. Saved index authority and read evidence are separate, and archived evidence cannot authorize Send. Stop or page exit revokes active work. This Skills contract makes no inference about Memory visibility. Build with `npx nx build cockpit-deep-agents-skills-react` and verify with `npx nx e2e cockpit-deep-agents-skills-react`; React remains a private source-build package. diff --git a/apps/website/content/docs/deep-agents/capabilities/skills.mdx b/apps/website/content/docs/deep-agents/capabilities/skills.mdx index d0eb42af1..288c504c2 100644 --- a/apps/website/content/docs/deep-agents/capabilities/skills.mdx +++ b/apps/website/content/docs/deep-agents/capabilities/skills.mdx @@ -96,7 +96,7 @@ file are the authority, and your recollection is not. ### Announcing Skills metadata -`SkillsMiddleware` annotates `skills_metadata` and `skills_load_errors` with `PrivateStateAttr`, excluding them from the declared input and output schemas. That does not hide the keys from every stream: the pinned Skills graph exposes `skills_metadata` in root `values` snapshots and saved checkpoints. The optional `skills_load_errors` key can appear when load errors are produced, and normal successful loads can omit it. `SkillsVisibilityMiddleware` always includes both keys in its named `custom` event, using an empty-list fallback for load errors and guarded because `get_stream_writer` raises outside a streaming context. This application event provides an explicit live signal; it is not proof that root values omit Skills metadata. +`SkillsMiddleware` annotates `skills_metadata` and `skills_load_errors` with `PrivateStateAttr`, excluding them from declared input and output schemas. The local compiled graph exposes metadata in root `values` snapshots and saved checkpoints. In the observed deployed API stream, root values omit private Skills keys while saved checkpoints retain metadata, consistent with output-schema filtering. The optional `skills_load_errors` key can appear when errors are produced, and successful loads can omit it. `SkillsVisibilityMiddleware` includes both keys in its named `custom` event, with an empty-list error fallback and a guard because `get_stream_writer` raises outside streaming. Angular uses this event for its live index. React reads native root values and, when the public stream omits metadata, the confirmed saved checkpoint. The custom event does not supply React index authority or confirmed-read proof. React confirmation allows only absent live private Skills keys; present private values, every ordinary value, messages, tools and terminal controls must agree with the exact checkpoint. Current state, newest history and the exact checkpoint must agree fully, as must values after native load. Saved checkpoint metadata remains authoritative. diff --git a/apps/website/content/docs/deep-agents/getting-started/introduction.mdx b/apps/website/content/docs/deep-agents/getting-started/introduction.mdx index f851ce776..a2257195e 100644 --- a/apps/website/content/docs/deep-agents/getting-started/introduction.mdx +++ b/apps/website/content/docs/deep-agents/getting-started/introduction.mdx @@ -70,7 +70,7 @@ Deep Agents does not publish all five capabilities the same way, and the differe **Tool calls are already structured.** The subagent `task` tool and the filesystem `read_file` tool arrive as normal tool calls. The subagent tracker recognizes `task` by default, so child agents render as cards with no client configuration at all. -**Private schema annotations need a capability-specific contract.** `PrivateStateAttr` excludes annotated keys from declared input and output schemas; it does not establish that every root stream omits them. The pinned Skills graph exposes `skills_metadata` in root values and saved checkpoints. The optional `skills_load_errors` key can appear there when load errors are produced; normal successful loads can omit it. A named application-side custom visibility event always includes both keys, using an empty-list fallback for load errors. Its page describes that verified behavior. Memory is a separate capability: the Skills tests do not establish its `memory_contents` visibility. +**Private schema annotations need a capability-specific contract.** `PrivateStateAttr` excludes annotated keys from declared input and output schemas. The local compiled Skills graph exposes metadata in root values and saved checkpoints; observed deployed API root values omit private Skills keys while checkpoints retain metadata, consistent with output-schema filtering. Optional `skills_load_errors` can appear when errors are produced, and successful loads can omit it. The named custom event includes both keys, with an empty-list error fallback, for Angular's live index. React reads native root values and, when the public stream omits metadata, the confirmed saved checkpoint. The event does not supply React index authority or prove a confirmed read; saved checkpoints remain authoritative, with native messages, tools and ordinary values checked separately. Memory is a separate capability: Skills observations do not establish its `memory_contents` visibility. ## The demos diff --git a/apps/website/public/AGENTS.md b/apps/website/public/AGENTS.md index 62ce6f017..5fc114860 100644 --- a/apps/website/public/AGENTS.md +++ b/apps/website/public/AGENTS.md @@ -127,4 +127,4 @@ The private React render entry also accepts an optional `ReactRenderFunctions` h Computed Functions has an experimental local React preview at https://threadplane.ai/docs/render/api/provide-render?frontend=react. Three authored samples preserve native $computed expressions and use RenderSpec.functions for uppercase/reverse text, products and browser-local dates. Only complete expression nodes calculate; numeric/date prefixes never publish provisional results. Heading/Card/Value display resolved props and native children. Pure synchronous callbacks receive owned frozen arguments and may run repeatedly. Play/Pause, seek, Finish and Reset share one bounded private parser owner; sample changes and page exit revoke old callbacks. It makes no backend or model requests. Build with `npx nx build cockpit-render-computed-functions-react` and verify with `npx nx e2e cockpit-render-computed-functions-react`. -The native Skills React preview at https://threadplane.ai/docs/deep-agents/capabilities/skills?frontend=react shares the existing Python Skills graph and enforced read-only /skills/ mount with Angular. Its prompt index contains skill names, descriptions, and instruction paths; bodies and references load on demand. PrivateStateAttr excludes Skills keys from declared input/output schemas, while the pinned graph exposes metadata in root values and checkpoints. Optional load errors can appear when produced and normal loads can omit them. Its named custom visibility event always includes both keys, using an empty-list fallback for load errors. The React preview distinguishes requested paths from successfully inspected tool results; Angular shows requested paths without result validation. Saved index authority and read evidence are separate, and archived evidence cannot authorize Send. Stop or page exit revokes active work. This Skills contract makes no inference about Memory visibility. Build with `npx nx build cockpit-deep-agents-skills-react` and verify with `npx nx e2e cockpit-deep-agents-skills-react`; React remains a private source-build package. +The native Skills React preview at https://threadplane.ai/docs/deep-agents/capabilities/skills?frontend=react shares the existing Python Skills graph and enforced read-only /skills/ mount with Angular. Its prompt index contains skill names, descriptions, and instruction paths; bodies and references load on demand. PrivateStateAttr excludes Skills keys from declared input/output schemas. The local compiled graph exposes metadata in root values and checkpoints; observed deployed API root values omit private Skills keys while saved checkpoints retain metadata, consistent with output-schema filtering. Optional load errors can appear when produced and normal loads can omit them. Its named custom event includes both keys with an empty-list error fallback for Angular's live index. React reads native root values and, when the public stream omits metadata, the confirmed saved checkpoint. The custom event does not supply React index authority or confirmed-read proof. Confirmation permits only absent live skills_metadata/skills_load_errors; every present private value and ordinary value must match the exact checkpoint, and loaded values must agree fully. Saved checkpoint metadata remains authoritative. The React preview distinguishes requested paths from successfully inspected tool results; Angular shows requested paths without result validation. Saved index authority and read evidence are separate, and archived evidence cannot authorize Send. Stop or page exit revokes active work. This Skills contract makes no inference about Memory visibility. Build with `npx nx build cockpit-deep-agents-skills-react` and verify with `npx nx e2e cockpit-deep-agents-skills-react`; React remains a private source-build package. diff --git a/apps/website/public/CLAUDE.md b/apps/website/public/CLAUDE.md index 62ce6f017..5fc114860 100644 --- a/apps/website/public/CLAUDE.md +++ b/apps/website/public/CLAUDE.md @@ -127,4 +127,4 @@ The private React render entry also accepts an optional `ReactRenderFunctions` h Computed Functions has an experimental local React preview at https://threadplane.ai/docs/render/api/provide-render?frontend=react. Three authored samples preserve native $computed expressions and use RenderSpec.functions for uppercase/reverse text, products and browser-local dates. Only complete expression nodes calculate; numeric/date prefixes never publish provisional results. Heading/Card/Value display resolved props and native children. Pure synchronous callbacks receive owned frozen arguments and may run repeatedly. Play/Pause, seek, Finish and Reset share one bounded private parser owner; sample changes and page exit revoke old callbacks. It makes no backend or model requests. Build with `npx nx build cockpit-render-computed-functions-react` and verify with `npx nx e2e cockpit-render-computed-functions-react`. -The native Skills React preview at https://threadplane.ai/docs/deep-agents/capabilities/skills?frontend=react shares the existing Python Skills graph and enforced read-only /skills/ mount with Angular. Its prompt index contains skill names, descriptions, and instruction paths; bodies and references load on demand. PrivateStateAttr excludes Skills keys from declared input/output schemas, while the pinned graph exposes metadata in root values and checkpoints. Optional load errors can appear when produced and normal loads can omit them. Its named custom visibility event always includes both keys, using an empty-list fallback for load errors. The React preview distinguishes requested paths from successfully inspected tool results; Angular shows requested paths without result validation. Saved index authority and read evidence are separate, and archived evidence cannot authorize Send. Stop or page exit revokes active work. This Skills contract makes no inference about Memory visibility. Build with `npx nx build cockpit-deep-agents-skills-react` and verify with `npx nx e2e cockpit-deep-agents-skills-react`; React remains a private source-build package. +The native Skills React preview at https://threadplane.ai/docs/deep-agents/capabilities/skills?frontend=react shares the existing Python Skills graph and enforced read-only /skills/ mount with Angular. Its prompt index contains skill names, descriptions, and instruction paths; bodies and references load on demand. PrivateStateAttr excludes Skills keys from declared input/output schemas. The local compiled graph exposes metadata in root values and checkpoints; observed deployed API root values omit private Skills keys while saved checkpoints retain metadata, consistent with output-schema filtering. Optional load errors can appear when produced and normal loads can omit them. Its named custom event includes both keys with an empty-list error fallback for Angular's live index. React reads native root values and, when the public stream omits metadata, the confirmed saved checkpoint. The custom event does not supply React index authority or confirmed-read proof. Confirmation permits only absent live skills_metadata/skills_load_errors; every present private value and ordinary value must match the exact checkpoint, and loaded values must agree fully. Saved checkpoint metadata remains authoritative. The React preview distinguishes requested paths from successfully inspected tool results; Angular shows requested paths without result validation. Saved index authority and read evidence are separate, and archived evidence cannot authorize Send. Stop or page exit revokes active work. This Skills contract makes no inference about Memory visibility. Build with `npx nx build cockpit-deep-agents-skills-react` and verify with `npx nx e2e cockpit-deep-agents-skills-react`; React remains a private source-build package. diff --git a/apps/website/src/components/docs/ReactDeepAgentsSkillsPreview.tsx b/apps/website/src/components/docs/ReactDeepAgentsSkillsPreview.tsx index e49dc06b0..3061753cf 100644 --- a/apps/website/src/components/docs/ReactDeepAgentsSkillsPreview.tsx +++ b/apps/website/src/components/docs/ReactDeepAgentsSkillsPreview.tsx @@ -17,9 +17,9 @@ Aviation examples use fixed sample data. Only explicit Send creates the owned th ## Inspect the index and actual reads -The index exposes names, descriptions, virtual instruction paths and optional support annotations from root **skills_metadata**. Declared input/output schema exclusions do not promise privacy: actual root stream values and checkpoints contain metadata. Metadata alone does not establish that instructions were read. Both runway-analysis and weather-brief remain index evidence even when only one has associated reads. +The index exposes names, descriptions, virtual instruction paths and optional support annotations from root **skills_metadata**. The local compiled graph exposes metadata in root stream values and saved checkpoints. Observed deployed API values omit private Skills keys while saved checkpoints retain metadata, consistent with declared output-schema filtering. Angular uses the custom event for its live index. React reads native root values and, when the public stream omits metadata, the confirmed saved checkpoint. The custom event does not supply React index authority or confirmed-read proof. Metadata alone does not establish that instructions were read. Both runway-analysis and weather-brief remain index evidence even when only one has associated reads. -**Observed root values** identifies live index evidence. **Confirmed checkpoint** requires matching current root state, fresh history head, exact checkpoint and the native transcript/load. Other actual root fields remain part of this authority. +**Observed root values** identifies live index evidence. **Confirmed checkpoint** requires matching current root state, fresh history head, exact checkpoint and the native transcript/load. Only absent live skills_metadata and skills_load_errors are permitted; present private values and every ordinary root value must agree with the checkpoint. Loaded values must agree fully. Saved checkpoint metadata remains authoritative. This Skills contract makes no inference about Memory visibility. diff --git a/cockpit/deep-agents/skills/angular/src/app/skills.component.ts b/cockpit/deep-agents/skills/angular/src/app/skills.component.ts index e0ce9804a..a4a945c86 100644 --- a/cockpit/deep-agents/skills/angular/src/app/skills.component.ts +++ b/cockpit/deep-agents/skills/angular/src/app/skills.component.ts @@ -33,8 +33,11 @@ const SUGGESTIONS = [ * The panel displays that index and paths requested through read_file. * * PrivateStateAttr excludes Skills keys from declared input/output schemas; - * the pinned graph exposes metadata in root values and checkpoints. Optional - * load errors can appear when produced and normal loads can omit them. The named + * the local compiled graph exposes metadata in root values and checkpoints. + * Observed deployed API values omit private Skills keys while saved checkpoints + * retain metadata. The named event supplies live index visibility, not proof of + * a confirmed read. Optional load errors can appear when produced and normal + * loads can omit them. The named * custom event always includes both keys, using an empty-list error fallback. * The panel prefers that event and falls back to agent.value(). Its checkpoint * label identifies that fallback, without proving reopened-thread provenance. diff --git a/cockpit/deep-agents/skills/python/src/graph.py b/cockpit/deep-agents/skills/python/src/graph.py index 4f7e2a526..6c23abab8 100644 --- a/cockpit/deep-agents/skills/python/src/graph.py +++ b/cockpit/deep-agents/skills/python/src/graph.py @@ -17,11 +17,14 @@ writes goes to the thread's own `StateBackend`. Visibility: `PrivateStateAttr` excludes Skills keys from declared input/output -schemas. The pinned graph exposes metadata in root values and checkpoints; -optional load errors can appear when produced, and normal loads can omit them. +schemas. The local compiled graph exposes metadata in root values and saved +checkpoints. The observed deployed API stream omits private Skills keys while +saved checkpoints retain metadata, consistent with output-schema filtering. +Optional load errors can appear when produced, and normal loads can omit them. `SkillsVisibilityMiddleware` always includes both keys in its custom event, using an empty-list fallback for load errors; -this verified Skills contract makes no inference about Memory. +The custom event is a live index signal, not confirmed-read proof. This Skills +contract makes no inference about Memory. """ from pathlib import Path @@ -131,11 +134,12 @@ def _seed_skills_store() -> InMemoryStore: class SkillsVisibilityMiddleware(AgentMiddleware): """Republish `skills_metadata` as a `custom` stream event. - Skills keys are excluded from declared input/output schemas. Root values - and checkpoints expose metadata; optional load errors can appear when - produced and can be omitted on normal loads. This live event always includes - both keys, with an empty-list fallback for load errors. It makes no claim - about Memory visibility. + Skills keys are excluded from declared input/output schemas. Local compiled + root values expose metadata; observed deployed API values omit private keys. + Saved checkpoints remain authoritative. Optional load errors can appear when + produced and can be omitted on normal loads. This live index event includes + both keys, with an empty-list fallback for errors; it does not prove a + confirmed read and makes no claim about Memory visibility. """ @property diff --git a/cockpit/deep-agents/skills/react/README.md b/cockpit/deep-agents/skills/react/README.md index 3e3a8f9e2..93754a853 100644 --- a/cockpit/deep-agents/skills/react/README.md +++ b/cockpit/deep-agents/skills/react/README.md @@ -6,6 +6,8 @@ The local selection is `deep-agents-skills`, with base path `/deep-agents/skills The index shows observed root metadata: each skill's name, description, virtual instruction path, and optional support annotations. Instruction bodies and references appear as on-demand tool read observations. An inspected read is evidence of the returned lines, not a claim that the whole file was read. Tool errors remain visible even when the parent response is confirmed. +The local compiled graph streams Skills metadata in root values. The observed deployed API stream omits private Skills keys, consistent with its declared output schema, while saved checkpoints retain metadata. Confirmation permits only absent live `skills_metadata` and `skills_load_errors`: present private values and all ordinary values must agree with the exact checkpoint, and native load must agree fully. Current state, newest history, exact checkpoint, messages, tools and terminal controls still need to agree. Angular uses the named custom event for its live index. React reads native root values and, when the public stream omits metadata, the confirmed saved checkpoint. The custom event does not supply React index authority or confirmed-read proof. This Skills behavior establishes no Memory visibility contract. + New clears the draft and view while retaining a bounded catalog of locally confirmed conversations. Selecting one reads fresh current state, history, the exact checkpoint, and the native session; it never sends a prompt. Reload clears the catalog. Stop or uncertain parent state requires New. No retry, replay, or resume controls are provided. Run `npx nx fixture-test cockpit-deep-agents-skills-react` for the source-bound compiled Python graph and installed SDK transport contracts, and `npx nx e2e cockpit-deep-agents-skills-react` for the installed native browser app. The loopback fixture uses deterministic model construction with remote sockets blocked, and preserves actual root values, checkpoints and tool results. Its controls and proof endpoints are excluded from the production frontend. These local checks do not establish live-service behavior. diff --git a/cockpit/deep-agents/skills/react/src/application.spec.ts b/cockpit/deep-agents/skills/react/src/application.spec.ts index 8e7b46a63..2c5b7eb84 100644 --- a/cockpit/deep-agents/skills/react/src/application.spec.ts +++ b/cockpit/deep-agents/skills/react/src/application.spec.ts @@ -66,6 +66,21 @@ it('confirms tool errors honestly without disabling an otherwise confirmed paren expect(x.app.getSnapshot().canSubmit).toBe(true); expect(x.app.getSnapshot().reads).toMatchObject([{ kind: 'error' }]); }); +it('confirms filtered live private values from the full checkpoint and keeps Send/history/read evidence available', async () => { + const x = setup(); + delete (x.input.snapshot.values as any).skills_metadata; + await x.app.submit('Inspect runway.'); + const view = x.app.getSnapshot(); + expect(view.canSubmit).toBe(true); + expect(view.history).toHaveLength(1); + expect(view.index).toMatchObject({ source: 'checkpoint' }); + expect(view.reads).toMatchObject([{ id: 'read', kind: 'inspected' }]); + expect(x.session.load).toHaveBeenCalledTimes(1); + await x.app.newConversation(); + await x.app.selectConversation('thread'); + expect(x.app.getSnapshot().canSubmit).toBe(true); + expect(x.session.submit).toHaveBeenCalledTimes(1); +}); it('New preserves known local history and selection freshly reads without model execution', async () => { const x = setup(); await x.app.submit('Inspect runway.'); @@ -442,6 +457,7 @@ it('malformed live state never reaches the display and requires New', async () = }); it('unsupported child state cannot masquerade as confirmed root skill evidence', async () => { const x = setup(); + delete (x.input.snapshot.values as any).skills_metadata; x.input.snapshot.subgraphs.push({ id: 'child' } as never); x.input.loadedSnapshot.subgraphs.push({ id: 'child' } as never); await x.app.submit('Inspect runway.'); diff --git a/cockpit/deep-agents/skills/react/src/authority.spec.ts b/cockpit/deep-agents/skills/react/src/authority.spec.ts index 9445495ac..7ad6132e8 100644 --- a/cockpit/deep-agents/skills/react/src/authority.spec.ts +++ b/cockpit/deep-agents/skills/react/src/authority.spec.ts @@ -7,6 +7,133 @@ import { } from './authority'; import { authorityInput } from './test-fixtures'; describe('terminal owned Skills authority', () => { + function withPrivateErrors() { + const input = authorityInput(); + for (const values of [ + input.observedState.values, + input.history[0].values, + input.loadedCheckpoint.values, + input.snapshot.values, + input.loadedSnapshot.values, + ]) { + (values as any).skills_load_errors = [ + { path: '/skills/broken/SKILL.md', error: 'Invalid frontmatter' }, + ]; + } + return input; + } + it.each([ + { keys: [] }, + { keys: ['skills_metadata'] }, + { keys: ['skills_load_errors'] }, + { keys: ['skills_metadata', 'skills_load_errors'] }, + ])( + 'confirms only absent live private keys $keys and retains exact checkpoint metadata', + ({ keys }) => { + const input = withPrivateErrors(); + for (const key of keys) delete (input.snapshot.values as any)[key]; + const authority = captureAuthority(input); + expect(authority.kind).toBe('terminal'); + if (authority.kind !== 'terminal') + throw Error('Unconfirmed private projection'); + expect(authority.values).toEqual(input.loadedCheckpoint.values); + expect(authority.rawToolStatuses).toEqual({ read: 'success' }); + } + ); + it.each(['skills_metadata', 'skills_load_errors'])( + 'rejects conflicting or present undefined private %s', + (key) => { + for (const value of [[], undefined]) { + const input = withPrivateErrors(); + delete (input.snapshot.values as any)[ + key === 'skills_metadata' ? 'skills_load_errors' : 'skills_metadata' + ]; + (input.snapshot.values as any)[key] = value; + expect(captureAuthority(input).kind).toBe('unconfirmed'); + } + } + ); + it.each(['skills_metadata', 'skills_load_errors'])( + 'requires loaded private %s even when live omits both', + (key) => { + for (const remove of [true, false]) { + const input = withPrivateErrors(); + delete (input.snapshot.values as any).skills_metadata; + delete (input.snapshot.values as any).skills_load_errors; + if (remove) delete (input.loadedSnapshot.values as any)[key]; + else (input.loadedSnapshot.values as any)[key] = []; + expect(captureAuthority(input).kind).toBe('unconfirmed'); + } + } + ); + it('rejects missing/conflicting ordinary keys, nested omissions, unknown extras and nonrecord live values', () => { + for (const modify of [ + (i: any) => { + delete i.snapshot.values.files; + }, + (i: any) => { + i.snapshot.values.files = { stale: true }; + }, + (i: any) => { + i.snapshot.values.extra = []; + }, + (i: any) => { + i.snapshot.values = structuredClone(i.snapshot.values); + delete i.snapshot.values.skills_metadata[0].description; + }, + ...[undefined, null, [], 'invalid'].map((value) => (i: any) => { + i.snapshot.values = value; + }), + ]) { + const input = authorityInput(); + modify(input); + expect(captureAuthority(input).kind).toBe('unconfirmed'); + } + }); + it('does not accept custom metadata when the checkpoint lacks it', () => { + const input = authorityInput(); + for (const values of [ + input.observedState.values, + input.history[0].values, + input.loadedCheckpoint.values, + input.loadedSnapshot.values, + ]) + delete (values as any).skills_metadata; + expect(captureAuthority(input).kind).toBe('unconfirmed'); + delete (input.snapshot.values as any).skills_metadata; + (input.snapshot as any).customEvents = [ + { name: 'skills_visibility', data: { skills_metadata: ['untrusted'] } }, + ]; + const authority = captureAuthority(input); + expect(authority.kind).toBe('terminal'); + if (authority.kind !== 'terminal') + throw Error('Unconfirmed ordinary values'); + expect(Object.hasOwn(authority.values, 'skills_metadata')).toBe(false); + }); + it('keeps transcript, tool, control and ownership gates when live private keys are omitted', () => { + for (const modify of [ + (i: any) => { + i.snapshot.messages[3].content = 'Different answer'; + }, + (i: any) => { + i.snapshot.toolCalls[0].result = 'Different result'; + }, + (i: any) => { + i.snapshot.interrupts = [{ id: 'interrupt' }]; + }, + (i: any) => { + i.owner = 'other-owner'; + }, + (i: any) => { + i.observedState.next.push('pending'); + }, + ]) { + const input = authorityInput(); + delete (input.snapshot.values as any).skills_metadata; + modify(input); + expect(captureAuthority(input).kind).toBe('unconfirmed'); + } + }); it('confirms current/fresh-history/exact/native-load equality and retains raw success/error', () => { expect(captureAuthority(authorityInput())).toMatchObject({ kind: 'terminal', diff --git a/cockpit/deep-agents/skills/react/src/authority.ts b/cockpit/deep-agents/skills/react/src/authority.ts index a43e775f5..8719e00bc 100644 --- a/cockpit/deep-agents/skills/react/src/authority.ts +++ b/cockpit/deep-agents/skills/react/src/authority.ts @@ -539,6 +539,28 @@ function matchesRoot( return true; } +/** Compare already-owned live values with the exact checkpoint projection. + * Deployed streams can omit the two private Skills keys. Every observed key + * still agrees exactly; every ordinary checkpoint key must be observed. */ +function matchesLiveValues( + live: unknown, + checkpoint: Record +): boolean { + if (!record(live)) return false; + return ( + Object.keys(live).every( + (key) => + Object.hasOwn(checkpoint, key) && sameData(live[key], checkpoint[key]) + ) && + Object.keys(checkpoint).every( + (key) => + Object.hasOwn(live, key) || + key === 'skills_metadata' || + key === 'skills_load_errors' + ) + ); +} + /** A failure yields no replacement: callers retain previously confirmed authority. * The caller captures its owned client and turn once before asynchronous reads. */ export function captureAuthority(input: unknown): Authority { @@ -618,7 +640,7 @@ export function captureAuthority(input: unknown): Authority { !raw || !matchesRoot(raw, before, prefix.length) || !matchesRoot(raw, after, prefix.length) || - !sameData(captured.snapshot.values, otherValues) || + !matchesLiveValues(captured.snapshot.values, otherValues) || !sameData(captured.loadedSnapshot.values, otherValues) ) return unavailable; diff --git a/cockpit/deep-agents/skills/react/src/native-contract.spec.ts b/cockpit/deep-agents/skills/react/src/native-contract.spec.ts index 758c19a9f..5ebd6f1b8 100644 --- a/cockpit/deep-agents/skills/react/src/native-contract.spec.ts +++ b/cockpit/deep-agents/skills/react/src/native-contract.spec.ts @@ -14,7 +14,16 @@ const actual = JSON.parse( 'utf8' ) ); -it('preserves actual root skills_metadata across native run, installed SDK history, and native load', async () => { +it.each([ + { + name: 'preserves actual root skills_metadata across native run, installed SDK history, and native load', + filtered: false, + }, + { + name: 'confirms filtered deployed public values through genuine native submit and installed SDK load', + filtered: true, + }, +])('$name', async ({ filtered }) => { const requests: string[] = []; const fetch = vi.fn( async (url: string | URL | Request, init?: RequestInit) => { @@ -59,7 +68,16 @@ it('preserves actual root skills_metadata across native run, installed SDK histo id: 'offline-human', content: 'Inspect bundled skills.', }); - yield { type: 'values', data: actual.rootValues }; + // Model the observed deployed public key set, retaining the compiled + // graph checkpoint unchanged. Both snapshots come from createSession. + const publicValues = Object.fromEntries( + Object.entries(actual.rootValues).filter( + ([key]) => + !filtered || + (key !== 'skills_metadata' && key !== 'skills_load_errors') + ) + ); + yield { type: 'values', data: publicValues }; }, getHistory: (thread) => sdk.threads.getHistory(thread), }, @@ -76,9 +94,18 @@ it('preserves actual root skills_metadata across native run, installed SDK histo ); await session.load?.(); const loadedSnapshot = session.getSnapshot(); - expect(snapshot.values?.skills_metadata).toEqual( - actual.current.values.skills_metadata - ); + if (filtered) { + expect(Object.keys(snapshot.values ?? {})).toEqual(['files']); + expect(Object.hasOwn(snapshot.values ?? {}, 'skills_metadata')).toBe( + false + ); + } else { + expect(snapshot.values?.skills_metadata).toEqual( + actual.current.values.skills_metadata + ); + } + expect(observedState).toEqual(history[0]); + expect(loadedCheckpoint).toEqual(observedState); expect(loadedSnapshot.values?.skills_metadata).toEqual( actual.current.values.skills_metadata ); diff --git a/scripts/react-parity/baseline.json b/scripts/react-parity/baseline.json index a65bad70a..60303657e 100644 --- a/scripts/react-parity/baseline.json +++ b/scripts/react-parity/baseline.json @@ -1,6 +1,6 @@ { "schemaVersion": 1, - "baselineHead": "8a0175a5248f9b9d11075d45e8961a53864907a4", + "baselineHead": "b654abd3dfed6932f364c27d32e219f585f4143e", "sourceState": { "modified": [], "untracked": [] @@ -2694,7 +2694,7 @@ "id": "doc:apps/website/content/docs/deep-agents/capabilities/skills.mdx", "kind": "doc", "path": "apps/website/content/docs/deep-agents/capabilities/skills.mdx", - "sha256": "4977c435bd7c84f436cd56062542b07a3b5de69248ead90e2210da58d62f0942" + "sha256": "ad260092841e9e00945e92845feed0d64660d3bc0ed9c681f664fc3505e73c1f" }, { "id": "doc:apps/website/content/docs/deep-agents/capabilities/subagents.mdx", @@ -2706,7 +2706,7 @@ "id": "doc:apps/website/content/docs/deep-agents/getting-started/introduction.mdx", "kind": "doc", "path": "apps/website/content/docs/deep-agents/getting-started/introduction.mdx", - "sha256": "a9515a3a1a8b2f1c0eaea972e50e931e2a453ad2ba0221122cdc16e2200f8cb1" + "sha256": "b03339b9fff465dce25d55c086c655b6206f31789efcf2795fc08d534e6538c2" }, { "id": "doc:apps/website/content/docs/langgraph/api/fetch-stream-transport.mdx",