Skip to content

Add explicit OpenProse context envelope - #100

Open
rawwerks wants to merge 1 commit into
codex/colocate-openprose-lintfrom
feature/openprose-context-envelope-pr
Open

Add explicit OpenProse context envelope#100
rawwerks wants to merge 1 commit into
codex/colocate-openprose-lintfrom
feature/openprose-context-envelope-pr

Conversation

@rawwerks

Copy link
Copy Markdown
Contributor

Summary

This adds an explicit worker-frame context boundary for OpenProse runs.

  • Defines ### Context as a service-local, read-only allow-list separate from ### Requires.
  • Adds a VM-authored worker launch envelope so runs can prove what context was sent to each worker.
  • Updates the session/runtime/filesystem/Forme docs so missing context fails closed instead of falling back to ambient parent context.
  • Updates CLI bootstrap behavior so forwarded Codex and Claude Code harness runs honor the active PROSE_OPENPROSE_ROOT, activation id, and explicit OpenProse skill-root override.

Why This Shape

### Requires and ### Context now have different jobs.

### Requires is the task interface: values the service needs in order to run.

### Context is a read-only background allow-list: material the VM is allowed to place inside that worker frame. If requested context is missing, the worker should fail closed or produce missing-context evidence instead of reconstructing it from ambient parent context.

That gives OpenProse a cleaner boundary for multi-agent systems: the parent can have broad context, private scratch, sibling outputs, and operational details, while each worker receives only the task, declared inputs, declared context, workspace path, and output instructions it needs.

To make that boundary auditable, the VM writes a worker launch envelope before spawning a worker. The envelope records what classes of input were included and which classes were intentionally absent. This is stronger than relying only on the worker's final answer, because it captures the launch boundary before the model runs.

Validation

The committed CLI tests cover the public contract surface: ### Context documentation, worker-launch-envelope documentation, active-root bootstrap instructions, shared Codex/Claude Code harness routing, explicit skill-root overrides, and fail-closed invalid override behavior.

In addition to those committed checks, I tested the shape with a local context-boundary run that deliberately mixed safe context with private parent-only material.

The run checked that:

  • the worker received the declared context binding;
  • the worker did not receive raw parent context;
  • the worker did not receive sibling outputs or undeclared context;
  • missing requested context failed closed instead of being invented;
  • worker-visible outputs contained the expected missing-context evidence;
  • private markers stayed out of worker-visible artifacts;
  • the worker launch envelope was runtime-authored and matched the declared context file by digest.

I also ran polarity controls for the envelope rule:

  • no envelope is treated as unproven;
  • a valid envelope passes;
  • an envelope containing disallowed context fails.

Those checks are why this PR uses both a declared ### Context section and a launch envelope: the section expresses intent, and the envelope proves what actually crossed the worker boundary.

The CLI bootstrap path is shared by the Codex and Claude Code harness adapters. This PR updates that shared path so forwarded runs receive the active OpenProse root, activation id, and skill-root override consistently. The focused CLI tests cover both harness routing and the shared bootstrap contract; the local runtime proof exercised the Codex harness path.

Local Checks

  • vitest --run tests/skills/open-prose.test.ts
  • tsc -p tsconfig.json --noEmit
  • tsc -p tsconfig.build.json
  • scoped ubs scan on changed files

@rawwerks
rawwerks force-pushed the feature/openprose-context-envelope-pr branch from 26ff19a to 885b7ab Compare June 27, 2026 15:01
@rawwerks
rawwerks changed the base branch from main to codex/colocate-openprose-lint June 27, 2026 15:01
@rawwerks
rawwerks force-pushed the feature/openprose-context-envelope-pr branch from 885b7ab to cbc215a Compare June 27, 2026 15:58
@rawwerks

Copy link
Copy Markdown
Contributor Author

I refreshed this PR as a stacked follow-up on #145 (codex/colocate-openprose-lint) and retargeted it so the review diff is the Context change rather than a month of unrelated mainline/linter movement.

Current shape:

  • base: codex/colocate-openprose-lint
  • head: feature/openprose-context-envelope-pr
  • commit: cbc215a / Add Context contract boundary support
  • diff: 46 files, 1544 insertions, 80 deletions

Why this is stacked on #145

#145 moved openprose-lint into this repo and made the linter/package gates part of the language repo workflow. That matters for this PR because ### Context should not be promoted as prose docs alone. If it lands, it needs to land with:

  • language documentation,
  • deterministic lint support,
  • runtime capability metadata,
  • Reactor compile/run behavior,
  • cache/fingerprint behavior,
  • and tests that prove it does not quietly become a hidden data channel.

The stacked shape lets those pieces evolve together in one repo. It also reduces drift: if ### Context is accepted by the language docs, the colocated linter and Reactor support are updated in the same branch.

What problem this PR is solving

When writing .prose.md programs for agents, authors often need to give the render session bounded grounding that is not itself runtime truth. Examples:

  • style guidance,
  • policy notes,
  • source paths or documentation pointers,
  • prior decisions,
  • interpretation rules,
  • operator guidance,
  • and constraints like "read this upstream payload through the tool, do not invent it."

Before this PR, authors could put that material somewhere informal, but the language did not give it a stable contract home. That creates two bad outcomes:

  1. reviewers cannot tell whether a section is intended to be executable data flow or just grounding;
  2. harnesses may accidentally treat ambient parent-session context as if it were part of the declared program.

This PR gives that material a named section: ### Context.

The design decision

### Context is deliberately bounded, read-only grounding.

It is not dependency injection. It is not a payload slot. It does not create Forme edges. It cannot satisfy ### Requires, ### Maintains, or ### Returns.

That distinction is the center of the change. The feature is useful only if it gives authors a clearer place to put grounding while preserving OpenProse's existing dependency model:

  • moving truth flows through gateways and maintained facets;
  • subscribers read that truth through ### Requires;
  • functions use parameters/returns;
  • Reactor renders read upstream truth by reference through tools;
  • Context stays as declared guidance that is visible, fingerprinted, inspectable, and bounded.

The shorthand is:

Context can tell the render how to interpret truth, but it is not where truth enters the system.

What changed in the language docs

The docs now define ### Context as an official Contract Markdown section for responsibility and function contracts.

The key language is:

  • Context declares read-only context the render may consult.
  • Context is preserved for compile/run evidence and contract identity.
  • Context does not create Forme edges.
  • Context does not satisfy Requires, Maintains, or Returns.
  • Harnesses that claim Context support should avoid silently adding undeclared ambient context to child renders.
  • If a child activation is used, the launch artifact should make the declared context boundary inspectable.

The updated files are:

  • skills/open-prose/contract-markdown.md
  • skills/open-prose/reactor.md
  • skills/open-prose/SKILL.md
  • skills/open-prose/changelog.md
  • CONTRIBUTING.md

The crates/openprose-lint/spec-snapshot/... copies intentionally mirror the source skill docs because the linter package gate verifies the packaged snapshot.

Public runnable example

This PR now includes a committed Context example, not just tests with generated temporary sources:

  • skills/open-prose/examples/context-boundary/README.md
  • skills/open-prose/examples/context-boundary/src/request-inbox.prose.md
  • skills/open-prose/examples/context-boundary/src/context-brief.prose.md

The example is intentionally small:

request-inbox (gateway, external-driven)
   maintains: request
   triggered with --data
        |
        | facet: request
        v
context-brief (responsibility, input-driven)
   requires: request
   context: read-only guidance for interpreting the request
   maintains: brief

The point of the example is to make the boundary inspectable:

  • request-inbox is the only place runtime payload enters;
  • context-brief has ### Context, but still requires the request facet from request-inbox;
  • the request payload is not put in Context;
  • the brief must read upstream request truth by reference and then publish state/brief.md.

How reviewers can run the example checks

The deterministic path is the one I would recommend reviewers start with. It runs offline, uses fake compile providers/fake renders, and exercises the committed example source through the Reactor CLI test path:

REACTOR_OFFLINE=1 pnpm --filter @openprose/reactor-cli build:test
REACTOR_OFFLINE=1 node --test packages/reactor-cli/dist-test/__tests__/compile.test.js
REACTOR_OFFLINE=1 node --test packages/reactor-cli/dist-test/__tests__/run.test.js
REACTOR_OFFLINE=1 node --test packages/reactor-cli/dist-test/__tests__/connectors.test.js

The important passing lines to look for are:

✔ preserves an explicit Context program through topology and run contract views
✔ stages --data, propagates request truth, and renders a Context-grounded subscriber
✔ the ONE-SHOT `reactor trigger <gateway> --data` mount folds the payload into the gateway (B3)
✔ the ONE-SHOT `reactor trigger <non-gateway> --data` path fails closed (B3)

The committed example also has ordinary source-shape coverage:

cargo run -p openprose-lint -- lint --profile strict skills/open-prose/examples/context-boundary/src
REACTOR_OFFLINE=1 pnpm test:examples

Expected result:

  • both context-boundary/src/*.prose.md files lint clean in strict mode;
  • the examples corpus conformance test passes with context-boundary included in the maintained Reactor examples list.

For a manual live Reactor run, from the example directory:

cd skills/open-prose/examples/context-boundary
reactor doctor
reactor compile
reactor topology
reactor trigger request-inbox --data '{"id":"ctx-demo-001","source_revision":"manual","goal":"Summarize what the Context boundary allows."}'
reactor receipts

What reviewers should see in a successful live run:

  • topology shows request-inbox as the external gateway and context-brief as an input-driven subscriber;
  • trigger output shows request-inbox and context-brief rendering;
  • the receipt trail includes at least the staged ingress arrival, gateway render, and subscriber render;
  • context-brief wakes from input, not from direct external payload delivery;
  • the brief names the request id/source revision from gateway-maintained request truth;
  • the exact prose may vary by model, but the data path should not: payload -> gateway Maintains -> subscriber Requires.

What changed in the linter

openprose-lint now understands ### Context as current OpenProse vocabulary.

Concretely:

  • ### Context is recognized as a known contract section, so using it does not produce drift noise.
  • Context body items are parsed into the current contract-section model.
  • Context does not count as a maintained facet, return value, or dependency edge.
  • Programs with Context now imply a runtime requirement named context-boundary.
  • Runtime manifests can declare whether a harness supports that boundary.
  • A fixture (context-grounded-summary.prose.md) exercises the behavior.

The capability tests prove two important things:

  • a program with ### Context reports requires["context-boundary"] = true;
  • a runtime manifest that lacks context-boundary support fails closed, and the fixture isolates that single blocker.

This is important because it gives us a deterministic way to say "this program uses Context; this harness says it can or cannot provide the declared context boundary."

What changed in Reactor compile/run

Reactor now preserves Context through the compile/run surfaces that matter for reviewability and cache correctness.

The branch updates:

  • contract loading,
  • contract-set input rendering,
  • contract images,
  • contract-set/per-node fingerprinting,
  • IR cache identity,
  • persisted contract views,
  • SDK run-project contract views,
  • and render instruction composition.

The important runtime behavior is that the render sees ### Context in its node contract, but upstream truth is still read by reference through the world-model tools.

This matters for cache correctness: editing ### Context changes contract identity. A Context edit is a real contract edit, so cache/fingerprint behavior should move. But adding a runtime payload should not be smuggled through Context; payloads still need to be modeled as gateway-maintained truth and connected with Requires / Maintains.

What changed in render instructions

The render instruction layer now includes Context in the node contract.

The wake header also became more explicit about upstream truth:

  • it names resolved upstream producers and facets;
  • it points renders at wm_list_upstream and wm_read_upstream;
  • it does not pre-stuff upstream truth into the prompt.

That distinction is intentional. Context can guide the render, but upstream facts still come from the declared world-model surfaces.

What changed in reactor trigger --data

This branch also hardens one-shot trigger payload delivery.

During testing, the risky path was:

reactor trigger <non-gateway> --data ...

If that were allowed, a user could effectively deliver hidden payload data directly to a non-gateway node. That bypasses the gateway Maintains -> downstream Requires structure we are trying to protect.

The new behavior is fail-closed:

  • reactor trigger <gateway> --data ... is allowed;
  • the payload is staged through the same connector ingress mechanism used by gateways;
  • the gateway publishes maintained truth;
  • downstream nodes wake through input fingerprints and declared Requires;
  • reactor trigger <non-gateway> --data ... exits nonzero with guidance to trigger a gateway instead.

This is a Context-related fix because it protects the boundary between declared grounding and runtime data. Without this, Context could be correct in the docs but the CLI would still have a back door for ambient input.

What changed around model config warnings

There is a small adjacent hardening patch around explicit temperature on reasoning-class models.

This came up while making the runtime path easier to exercise with current model families. The practical issue is that some reasoning-class models reject or behave oddly with explicit temperature unless the provider/model config handles it intentionally.

The branch now:

  • keeps omitted temperature truly omitted instead of silently defaulting it;
  • preserves explicit temperature: 0 when the author really sets it;
  • parses reasoning_effort;
  • warns in doctor/config tests when a reasoning-class OpenAI or Claude model has explicit temperature without an appropriate reasoning setting;
  • documents that deleting the temperature: line is often the right move.

This part is not the essence of ### Context, but it keeps the local Reactor harness from failing for a reason unrelated to the language feature while people dogfood it.

Tests added to this PR

Yes, this PR adds repo tests outside the linter. I treated the linter tests as necessary but not sufficient: the linter can prove the source shape and declared runtime requirement, but Reactor tests need to prove the feature survives compile/run and does not become a hidden input channel.

The branch adds or extends tests in these existing OpenProse test locations:

  • packages/reactor-cli/src/__tests__/compile.test.ts
  • packages/reactor-cli/src/__tests__/contract-images.test.ts
  • packages/reactor-cli/src/__tests__/connectors.test.ts
  • packages/reactor-cli/src/__tests__/run.test.ts
  • packages/reactor/src/adapters/agent-compile/__tests__/contract-loader.test.ts
  • packages/reactor/src/adapters/agent-render/__tests__/instructions.test.ts
  • packages/reactor/src/adapters/agent-render/__tests__/tools.test.ts
  • packages/reactor/src/sdk/__tests__/run-project.test.ts

The tests are deterministic/offline. They use fake structured compile providers and fake renders instead of spending model calls, which matches the existing Reactor test style in this repo.

What the non-linter tests cover

Contract parsing and source slicing:

  • a .prose.md file with ### Context is parsed with a context field;
  • Contract Markdown section names are recognized case-insensitively, so ### context works like ### Context;
  • contract-set input rendering preserves Context rather than dropping it.

Fingerprint/cache identity:

  • contract images preserve Context;
  • editing only the Context body moves the contract-set fingerprint;
  • editing only the Context body moves the per-node contract fingerprint;
  • persisted run-phase contract views include Context;
  • SDK runProject contract views include Context.

Render instruction behavior:

  • the node contract layer includes Context;
  • Context is placed before Maintains, so it is visible as grounding before the render sees what it must leave true;
  • the wake header names upstream producer/facet pairs;
  • the wake header points the render to wm_list_upstream and wm_read_upstream;
  • upstream truth is not pre-stuffed into the prompt.

Runtime behavior under Reactor:

  • the test builds a real temporary .prose.md mini-project with a gateway (request-inbox) and a downstream responsibility (context-brief);
  • the gateway Maintains a request facet;
  • the downstream responsibility Requires that request facet;
  • the downstream responsibility has a ### Context section telling it how to interpret the request and to read upstream truth through wm_read_upstream;
  • reactor trigger request-inbox --data ... stages the payload into gateway ingress;
  • the gateway publishes structured request truth;
  • the downstream responsibility wakes from moved input fingerprints;
  • the downstream responsibility reads the request from upstream world-model state;
  • the downstream responsibility publishes a bounded brief containing the request id and source revision from the staged truth;
  • receipts are persisted for ingress, gateway, and subscriber;
  • receipt chains verify.

This is the deterministic end-to-end test for the branch. It runs through the actual Reactor CLI/SDK path for a generated .prose.md program:

.prose.md source files
  -> compile with fake structured providers
  -> persisted compiled IR / contract views
  -> reactor trigger <gateway> --data
  -> gateway ingress staging
  -> gateway world-model publish
  -> downstream subscriber wake by moved input fingerprint
  -> render reads upstream truth by reference
  -> subscriber world-model publish
  -> persisted receipts and receipt-chain verification

The test intentionally uses fake providers and a fake render, so it is fully offline and deterministic. It proves the language/runtime plumbing and boundary behavior. It does not claim live-model quality or live-provider behavior; those should be evaluated separately once the deterministic contract is accepted.

Negative/runtime-boundary behavior:

  • reactor trigger <non-gateway> --data ... now fails closed;
  • the test verifies that the command exits nonzero, explains that --data can only target an external gateway node, and does not stage ingress truth for the non-gateway path.

That negative test is important. It is what gives me confidence that Context did not accidentally create a second, implicit payload lane.

How to reproduce the E2E checks

From this PR branch, the broad deterministic package checks are:

REACTOR_OFFLINE=1 pnpm --filter @openprose/reactor test
REACTOR_OFFLINE=1 pnpm --filter @openprose/reactor-cli test
bash scripts/lint-prose.sh ci
bash scripts/lint-prose.sh package

If you want to focus specifically on the Reactor CLI test files that exercise the Context/gateway path and the --data boundary, run:

REACTOR_OFFLINE=1 pnpm --filter @openprose/reactor-cli build:test
REACTOR_OFFLINE=1 node --test packages/reactor-cli/dist-test/__tests__/run.test.js
REACTOR_OFFLINE=1 node --test packages/reactor-cli/dist-test/__tests__/connectors.test.js

The main positive E2E case is in packages/reactor-cli/src/__tests__/run.test.ts:

stages --data, propagates request truth, and renders a Context-grounded subscriber

The main negative boundary case is in packages/reactor-cli/src/__tests__/connectors.test.ts:

the ONE-SHOT `reactor trigger <non-gateway> --data` path fails closed (B3)

Linter tests added

The linter tests are still part of the confidence story, but they cover a different layer:

  • crates/openprose-lint/fixtures/current/context-grounded-summary.prose.md is a current-language fixture using ### Context;
  • capabilities output reports requires["context-boundary"] = true;
  • runtime compatibility fails closed when the selected runtime manifest lacks context-boundary;
  • the fixture isolates that blocker so the diagnostic is about Context boundary support, not unrelated capability gaps.

Why this test style is customary here

This is consistent with the current OpenProse/Reactor repo style:

  • TypeScript package tests live next to package code under packages/*/src/**/__tests__/*.test.ts;
  • Rust linter tests live under crates/openprose-lint/tests;
  • the Reactor test suite already uses deterministic fake providers/fake renders for compile/run behavior;
  • live model tests exist elsewhere, but the default CI path favors offline deterministic tests;
  • repo scripts already expose this split through commands like pnpm --filter @openprose/reactor test, pnpm --filter @openprose/reactor-cli test, and bash scripts/lint-prose.sh ci.

So for a language/runtime change like this, the normal bar is not just "the docs changed" or "the linter accepts it." The bar is: parse it, preserve it, fingerprint it, render it, run a small Reactor program through it, and add at least one fail-closed test for the boundary we care about.

Local commands run

I ran the local gates on the final commit:

REACTOR_OFFLINE=1 pnpm --filter @openprose/reactor test
REACTOR_OFFLINE=1 pnpm --filter @openprose/reactor-cli test
bash scripts/lint-prose.sh ci
bash scripts/lint-prose.sh package
git diff --check HEAD~1..HEAD

I also ran a gitleaks scan of the exact patch; no leaks were found in this branch's patch.

GitHub checks on the refreshed PR are passing at the time of this comment, including:

  • OpenProse Lint
  • CI - Reactor Package
  • CI - Examples Gate (offline)
  • OpenProse smoke cases
  • Plugin Manifest

What this PR proves

This PR proves the deterministic/core contract behavior:

  • the language docs have a stable place for Context;
  • the linter recognizes Context and models its runtime requirement;
  • Context is preserved through compile/run artifacts;
  • Context changes contract identity;
  • renders see Context as contract guidance;
  • upstream truth still flows through declared world-model tools;
  • --data payloads cannot bypass gateways into non-gateway nodes.

What this PR does not claim

This PR does not claim that every possible hosted agent runtime has good live Context support yet.

The deterministic contract and Reactor surfaces are in place first. That gives us something stable to run broader live harness evaluations against. The next step after this lands would be to use the linter/runtime metadata plus Reactor traces to compare actual harness configurations and decide which ones deserve to claim Context support.

Review guidance

The highest-value review questions are:

  1. Is ### Context the right name and boundary for this feature?
  2. Is the "read-only grounding, not data flow" rule clear enough in the docs?
  3. Does it feel right that Context is fingerprinted as part of contract identity?
  4. Does it feel right that Context implies a runtime context-boundary capability?
  5. Is fail-closing reactor trigger --data for non-gateway nodes the right enforcement point?
  6. Is the small model-config warning patch acceptable in this PR, or should it be split out?

Known follow-up

There is one broader hardening item I would keep separate from this PR:

Today, upstream reads are authorized by subscribed producer. A stricter future pass could narrow wm_read_upstream further by facet, so a subscriber authorized for producer request-inbox / facet request cannot read unrelated facets from the same producer. That is adjacent to this Context work, but not required to land the Context boundary itself.

Why I think this is ready for review

The branch now treats ### Context as a real language feature rather than a docs-only addition:

  • documented in the skill/spec text,
  • understood by the linter,
  • represented in runtime capability checks,
  • preserved through Reactor compile artifacts,
  • visible to render sessions,
  • included in cache identity,
  • tested with an actual .prose.md gateway/subscriber program under Reactor,
  • and guarded against the main accidental abuse case: using hidden payload data as if it were declared context.

That is the bar I think we want for new OpenProse language features: language shape, deterministic checks, runtime behavior, and reviewable artifacts moving together.

-- Ray's friend Codex

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.

1 participant