Skip to content

CRW-852: carry a pair's autoCompactTokenLimit as thread config - #814

Merged
thisisjun786 merged 12 commits into
devfrom
codex/crw-852-auto-compact-limit
Oct 8, 2026
Merged

thisisjun786 merged 12 commits into
devfrom
codex/crw-852-auto-compact-limit

Conversation

@thisisjun786

@thisisjun786 thisisjun786 commented Oct 7, 2026 •

Copy link
Copy Markdown
Owner

What this changes

config.model_auto_compact_token_limit now reaches the host on the paths this issue names - the
bridge's thread/start (create_thread, create_worktree_thread) and thread/resume
(send_message_to_thread), the relay's managed start, and the relay's delivery and correction
resume - taken from the execution policy's role pair. It is not carried by every resume in the
repository: crw manage child-resume builds its own config and is listed under limitations below.

Problem. An inferhub/deepseek-v4.1-flash child whose input passes about 664,000 tokens is cut
by the provider (Provider stream error: upstream error) while the host reports a 950,000 window,
so Codex never compacts first and the child stops without a receipt.

Size. 26 files: implementation 339 added and 36 removed lines across 12 files (the issue's
estimate was 80 to 200), tests 840 added lines, 2 documents and the re-recorded plugin version.
The estimate is exceeded because the limit has to be carried on four separate send paths (the
bridge's create and resume, the relay's managed start, and the relay's delivery and correction
resume) and each path is pinned by its own fakehost test; the two correction rounds added the
receipt record and the bound-role read.

Behaviour after this change. A role pair - the single model/reasoningEffort form, or one
entry of pairs - may declare autoCompactTokenLimit, a positive integer. When the pair holds it:

  • create_thread, create_worktree_thread and the relay's managed start (which creates through
    bridge.CreateThread) send it on thread/start;
  • send_message_to_thread sends it on thread/resume;
  • a relay delivery or correction send carries it too. That send resumes from the task's recorded
    settings, which come from the host's creation response and so can never hold this value, so the
    limit is resolved from the policy by the pair the record states and added to the resume config -
    on the settings-free route as well, which transmits no pair but may still carry the limit of the
    pair the record says the thread is on. A record on a pair that declares no limit sends none rather
    than inheriting a sibling pair's, and a record citing an exception sends none.

A policy that declares no limit sends no key and behaves exactly as before. A value that is not a
positive integer - a string, a boolean, 0, a negative, a fraction, an exponent, null, NaN,
Infinity, an integer past int64 - is refused while the policy is read, through the existing
execution_policy_unreadable path; no new refusal name appears, and the refusal allowed lists and
the get_capabilities description are unchanged (that description is the authorization surface, as
it is for MCP profiles).

The host has no field that reports the value back, so the resume observation does not compare it. Its
silence withholds no message and fails no creation. The receipt records it under requested (with
the value that was sent) and unobservable, and never under verified; verification stays
observed_at_creation or observed_at_resume for the settings that were actually compared, and
not_requested when the limit was the only thing asked for. A relay resume records the same
observation on the receipt it keeps for its own send, because the host never reports the value back
and the stored receipt would otherwise lose it; a resume that carried no limit records no such
observation.

Acceptance criteria

  • c1 - the pair field is read on both spellings, refused through the existing policy error path,
    and carried by settings.Contract.Config() on thread/start and thread/resume including the
    relay's correction delivery; the value is not compared in the resume observation and is recorded as
    transmitted and unobservable; no new refusal name; the policy file itself is not edited;
    docs/role-execution-policy.md and the crw-run bridge reference state the field, the recommended
    550000 and its evidence.
  • c2 - the tests below were red on the baseline and are green here.
  • c3 - gofmt, go vet (default and GOOS=darwin), make lint, git diff --check,
    the plugin checks and the hosted CI are below.
  • c4 - one non-draft PR into dev; the CI results for this head are in a comment on this PR.
  • c5 - the scope, and the files edited outside the node's declared edit regions, are below.

Verification

Red first, on the baseline commit efe61652fb90e942f609f40f9e0110ab125c3880, with the new test
files copied onto it:

$ go test -count=1 -run 'AutoCompact|ThePolicyDescription|ALimitAlone' \
    ./internal/bridge/execution/ ./internal/bridge/settings/ ./internal/bridge/ ./internal/relay/adapter/
internal/bridge/execution/autocompact_test.go:22:52: role.Pairs[0].AutoCompactTokenLimit undefined (type RolePair has no field or method AutoCompactTokenLimit)
internal/bridge/settings/autocompact_test.go:12:66: unknown field AutoCompactTokenLimit in struct literal of type Contract
internal/bridge/autocompact_test.go:77:64: undefined: settings.AutoCompactTokenLimitKey
internal/relay/adapter/autocompact_test.go:130:38: undefined: settings.AutoCompactTokenLimitKey
FAIL

and a behaviour-level red that compiles on the baseline:

$ go test -count=1 -run TestRedAPolicyDeclaringTheLimitLoads ./internal/bridge/execution/
--- FAIL: TestRedAPolicyDeclaringTheLimitLoads (0.00s)
    a policy declaring autoCompactTokenLimit does not load: execution_policy_unreadable:
    role "child" has unknown keys ["autoCompactTokenLimit"]; supported are
    ["expectation","mcp","model","pairs","reasoningEffort"]

Green on the head of this PR:

$ go test -count=1 ./internal/bridge/... ./internal/relay/managed/ ./internal/relay/adapter/
ok  github.com/thisisjun786/codex-relay-workflow/internal/bridge
ok  github.com/thisisjun786/codex-relay-workflow/internal/bridge/execution
ok  github.com/thisisjun786/codex-relay-workflow/internal/bridge/settings
ok  github.com/thisisjun786/codex-relay-workflow/internal/relay/managed
ok  github.com/thisisjun786/codex-relay-workflow/internal/relay/adapter
$ gofmt -l internal/                                            # no output
$ go vet ./internal/bridge/... ./internal/relay/adapter/         # clean
$ GOOS=darwin go vet ./internal/bridge/... ./internal/relay/adapter/   # clean
$ make lint                                                     # clean
$ git diff --check                                              # clean
$ go run -tags dev ./cmd/crw-dev ci validate
Validated 34 skills, local link paths and no Python outside skill assets.
No blob over 2 MiB comes into the history (every commit reachable from HEAD).
$ go run -tags dev ./cmd/crw-dev ci plugin
Package 0.4.0+0af82bb3897e at 314149dbdde2be9d062ebca571aa71c267e5dfe3: 216 files, digest 93059f603482a771. The version's suffix is these same files digested with that suffix elided
Skill names under the plugin namespace: crw:crw-check, crw:crw-define, crw:crw-logic, crw:crw-loop, crw:crw-next, crw:crw-plan, crw:crw-refactor, crw:crw-run, crw:crw-status, crw:crw-tidy

The head was also built and vetted on a temporary merge with origin/dev (a5e850460, later than
this branch's base): git merge-tree --write-tree HEAD origin/dev exits 0 with no conflict, and the
merged tree builds, vets and passes go test -count=1 ./internal/bridge/... ./internal/relay/adapter/.

Review findings

  • Devin (red, internal/bridge/create.go): relay resumes lose the limit. Correct, and the
    criteria name the relay correction delivery, so it is fixed rather than deferred:
    internal/relay/adapter/autocompact.go resolves the limit from the policy by the pair the record
    states and guardedSend adds it to the resume config on both routes. Tests:
    TestARelayDeliveryResumeCarriesTheRecordedPairsAutoCompactLimit (transmitted and settings-free),
    TestARelayDeliveryOnAPairWithoutALimitSendsNone, TestARelayDeliveryCitingAnExceptionSendsNoLimit.
  • Codex (P2, the crw-run bridge reference): state the recommendation and its evidence. Correct;
    the reference now names 550000 and the probe behind it.
  • Independent audit (PASS, four notes). One was folded: a settings-free resume's refusal said
    "nothing was transmitted" although that resume can now also carry the limit, so the refusal now
    names what went out, pinned by TestASettingsFreeRefusalNamesTheLimitItSent. The rest are
    recorded as limitations below rather than changed.

Contract and golden impact

None. No contract file, no refusal reason, no exit code, no CLI and no golden changed, so
crw-dev ci contracts was not needed. The settings receipt gains one entry in requested and
one in unobservable when, and only when, a pair declares the limit.

Correction generation 2 (the parent's pre-merge evaluation)

The parent's evaluation of head e1a06e2c found one P1 and three P2 defects, all introduced by this
change. All four are fixed here.

  • d1 (P1) - a relay resume lost the limit on its receipt. The send resolved the limit from the
    policy and put it in the resume config, but recorded only a transient flag on the copied settings,
    so the stored and replayed receipt carried neither the value nor the fact that the host does not
    report it. The receipt of every relay resume that sends the limit now records it in the same
    notation the bridge uses: the value under requested, the field under unobservable,
    not_requested, and nothing under verified; a resume that carried no limit records no such
    observation. Red first on the previous head:
$ go test -count=1 -run 'ARelayResumeReceiptRecords|ARelayResumeWithoutALimit' ./internal/relay/adapter/
--- FAIL: TestARelayResumeReceiptRecordsTheLimitItSentAsUnobservable/with-pair
    the receipt does not record the limit it sent as unobservable: map[... status:accepted ...]
--- FAIL: TestARelayResumeReceiptRecordsTheLimitItSentAsUnobservable/settings-free

Green here, on both the pair route and the settings-free route, including the stored receipt read
back through GetOperation and a replayed send. A send whose pair declares no limit records no
settings observation at all (TestARelayResumeWithoutALimitRecordsNoSettingsObservation).

  • d2 (P2) - the outside-region list omitted a file. internal/relay/adapter/settings.go is
    where the refusal wording this change alters actually lives; it is now named with its reason in
    "Files edited outside this node's declared edit regions" above.
  • d3 (P2) - the reference called the recommendation a measurement. The issue measured compaction
    at a limit of 4000; 550000 is the recommended margin below the observed provider limit, not a
    measured threshold. plugins/crw/skills/crw-run/references/bridge.md and
    docs/role-execution-policy.md now say exactly that, and the bridge reference describes the
    receipt record as built after d1 rather than promising one the code did not make.
  • d4 (P2, PR text only) - the first sentence over-promised. It said every thread/start and
    thread/resume this repository makes carries the limit; crw manage child-resume does not. The
    sentence now names the paths the issue names, and the gap is a limitation below.

Correction generation 2, second pass (the evaluation of head 314149db)

The evaluation of this generation's first head found one P1 and two P2 defects, all introduced by
this change. They are fixed here.

  • P1 - the limit was read only from the record's own citedRole. A record need not cite its own
    role: the sender's gate resolves the role the task is bound to (from the store binding) and
    authorizes the send against that role's pair. Reading the role from the record alone found none and
    sent no limit on a delivery the gate had accepted, which is exactly the case the limit exists to
    cover. The role the gate confirmed now travels with the send as delivery.TaskSettings.BoundRole
    and autoCompactLimit reads it before the record's citation. Red first:
$ go test -count=1 -run 'ReadsTheLimitFromTheBoundRole' ./internal/relay/adapter/
--- FAIL: TestARelayResumeReadsTheLimitFromTheBoundRoleTheGateConfirmed
    thread/resume config = map[model_reasoning_effort:xhigh sandbox_workspace_write:map[...]]

Green here on both routes, with TestTheDefaultRoleGateReportsTheBoundRoleItAuthorizedAgainst
pinning the gate half (the role comes from the same read that authorized the send, and a task
bound to no role reports none).

  • P2 - the outside-region list still omitted two files. internal/relay/adapter/json.go (the new
    settings key's place in the stored receipt's key order) and internal/relay/adapter/settings.go
    (the refusal wording) are now named with their reasons, and the flag name the list quoted is the
    field the code actually has.
  • P2 - the same over-promise in the first sentence. Corrected at the top of this body, and
    restated in the limitations below.

The independent reviewer (GLM 5.3, high, a fresh context) read the whole diff of head 314149db
against its merge base with dev and returned PASS WITH NOTES. Its one substantive note - a bound
role whose record cites none sends no limit - is the P1 above, which this push fixes. Its other
notes are the PR-body accuracy items also fixed here; it recorded no P0, P1, red or security
finding against the reviewed head.

Correction generation 2, third pass (the evaluation of head e9271aa70)

The evaluation of the second pass's head found one P1, introduced by this change: the policy
editor could not carry the new field.

  • P1 - setRolePairs silently dropped a pair's limit. The editor rebuilds a role's whole pairs
    list from the model and effort of each pair a change states, so a change that retained a limited
    pair published the same pair without its limit, and validation accepted it. A legacy single
    declaration that carried the limit could not be moved to the pairs form at all: the editor left
    the limit beside the list, where the parser refuses it. Red first, on head e9271aa70:
$ go test -count=1 -run TestRedEditorDropsTheLimit ./internal/policystore/
document={...pairs with autoCompactTokenLimit 550000...} ACCEPTED but the limit is gone
document={...legacy single declaration with the limit...} REFUSED:
  execution_policy_unreadable: role "child" declares both pairs and ["autoCompactTokenLimit"]

Green here: the limit travels with the pair (Pair.AutoCompactTokenLimit, written only when the
pair declares one), a retained pair keeps the limit the document already declared for its model
and effort, a legacy declaration's limit follows its one pair into the list, and Read projects
the limit onto the pair that declares it. Tests:
TestSetRolePairsKeepsTheLimitOfAPairItRetains, TestSetRolePairsMovesALegacyLimitIntoItsPair,
TestReadingShowsTheLimitOnThePairThatDeclaresIt.

Correction generation 2, fifth pass (the evaluation of head 569d5180)

The evaluation of the fourth pass's head found one more defect introduced by this change.

  • P1 - crw manage child-resume reloaded a capped child without the limit. That command
    resumes a recorded child from the relay's settings-show answer, and that record cannot hold
    this value either (the host never reports it back). It therefore reloaded a capped child under
    the host's own threshold and reported success, which is exactly the failure the limit exists to
    prevent. It now resolves the limit from the execution policy by the pair the record states,
    using the role the relay's gate confirmed the task is bound to (falling back to the record's own
    citation), and puts it in the thread/resume config. A record that cites an exception, a role
    the policy does not declare, or a pair that declares no limit sends no key, as the relay's own
    resume does; a configured policy that cannot be read is a refusal rather than a silent omission.
    Red first on head 569d5180, green here, pinned by
    TestResumeCarriesTheRecordedPairsAutoCompactLimit and TestResumeWithoutAPolicyLimitSendsNoKey.

The independent reviewer (GLM 5.3, high, a fresh context) reviewed head e9271aa70 (the bound-role
fix) and returned PASS WITH NOTES with no P0, P1, red or security finding; its one substantive
note, that both documents should say which role read wins, is folded into this push.

Correction generation 2, fourth pass (the evaluation of head 4a9a7b73)

The evaluation of the third pass's head found two more defects introduced by this change.

  • P1 - the editor accepted a supplied limit of 0 or null. The editor's pair type collapsed
    the supplied value into an int64, so 0 and null were read as "no limit": the check approved
    a document whose pair silently kept an older limit, or dropped the invalid value, and a fraction
    or a string failed the decode (answered as a bad request rather than the refused change the rule
    is). The pair now keeps the value exactly as the request spelled it and writes that spelling into
    the candidate document, where the same parser rule the policy file is held to refuses it. Red
    first on head 4a9a7b73, green here, with
    TestSetRolePairsRefusesASuppliedLimitThatIsNotAPositiveInteger over 0, -1, null, a
    fraction and a string.
  • P2 - both documents omitted the issue's measured application restriction. The issue's own
    observation says a resume to a thread the host already holds answers with the thread's current
    state, so the value applies at creation or when the host loads the thread again. Both
    docs/role-execution-policy.md and the crw-run bridge reference now say so explicitly: an
    accepted resume receipt records that the limit was sent, not that it applies.

Files edited outside this node's declared edit regions

  • internal/bridge/worktree.go - create_worktree_thread is a path criterion c1 names; both of
    its settings.Contract literals (the launch and the placement observation) now carry the
    authorized limit.
  • internal/relay/adapter/autocompact.go (new) and internal/relay/adapter/transport.go - the
    relay's delivery and correction send builds its resume parameters from the task record, which
    cannot hold this value, so the limit is resolved from the policy there and added to the resume
    config. This is criterion c1's "relay correction delivery" path.
  • internal/relay/adapter/autocompact_test.go (new) - the fakehost relay managed-start test and the
    relay-delivery tests. internal/relay/managed cannot import internal/relay/adapter (that
    package imports it), so a managed start over the production bridge and adapter can only be
    exercised from internal/relay/adapter; internal/relay/managed/autocompact_test.go therefore
    does not exist.
  • internal/relay/delivery/settings.go - the settings-free refusal's wording fix above. The task
    record gained two fields the send sets: AutoCompactTokenLimit, the value the resume put in the
    config (the value is not in the record and neither verifyResume nor the receipt can infer it),
    and BoundRole, the role the sender's gate confirmed the task is bound to (see the correction
    section below). The two
    wordings that were already there are unchanged, so the captured adapter goldens are unaffected
    (their policy declares no limit).
  • internal/relay/adapter/settings.go - the file that actually carries those refusal sentences.
    Two of them now name the pair's auto-compaction limit among what a settings-free resume sent,
    which is the wording fix described above; the switch it reads moved from a boolean flag to the
    limit value the send resolved.
  • internal/relay/adapter/json.go - the guarded send's receipt now carries a settings key, and
    receiptObject is the list that fixes the key order every stored receipt is serialized in, so
    the new key is named there (before settingsFindings, which is where the send writes them).
  • internal/relay/adapter/mcp_profile.go, internal/relay/adapter/daemon.go,
    internal/relay/adapter/cli.go, internal/relay/registry/record.go and
    internal/relay/supervisor/proposal.go - the bound-role plumbing of the correction section
    below: the role the gate confirmed travels with the send (the MCP copy keeps it, the two settings
    loaders report it, the registry exposes it from the same read that authorized the send, and the
    supervisor's settings-fence comparison includes it so a re-read that lands on a different binding
    still cancels the claim).
  • internal/policystore/policy.go and internal/policystore/check.go - the policy editor has to
    know the new field or it silently drops it. setRolePairs rebuilds a role's whole pairs list, so
    the pair type and its wire form gained the limit, the read projection shows it on the pair that
    declares it, and a retained pair keeps the limit the document already declared for its model and
    effort (a legacy single declaration's limit follows its one pair into the list). See the third
    correction section below.
  • internal/manage/childresume.go - crw manage child-resume resumes a recorded child from the
    relay's settings-show answer, which cannot hold the limit either, so it resolves the limit the
    same way the relay's own resume does. See the fifth correction section below.

Contract line citations (D-19)

Re-checked against dev at efe61652. The parent decision comments name no file:line this change
touches, and the contract sections this issue consumes are the issue body's own sections, which carry
no line citations of their own. The one source citation the issue makes - create.go 137,
settings.Contract.Config - still holds: the thread/start config is built from
settings.Contract.Config() in internal/bridge/create.go. No drift found.

Limitations and follow-ups (not done here)

  • internal/manage/childresume.go builds a thread/resume config directly and does not carry the
    limit. That path is outside this issue's four transmit paths (bridge create/resume, relay managed
    start, relay delivery/correction); whether it should carry the value is a decision for the issue
    that owns it.
  • When a record's role is declared but its model and effort match none of that role's pairs, the
    resume sends no limit and says nothing about it on the receipt. That follows the documented
    no-inheritance rule (a pair that declares no limit never inherits a sibling's), but a policy edit
    that renames a pair would silently drop the value.
  • The limit reaches the host on the paths this issue names. crw manage child-resume builds its own
    thread/resume config and does not carry it; whether it should is a decision for the issue that
    owns that path, not an extension of this one.
  • The live execution policy file is untouched; an operator writes autoCompactTokenLimit: 550000
    onto the pair after install, which is when the value starts to apply.

An execution-policy pair, and the legacy single model/reasoningEffort
declaration that is the same pair written another way, may now carry an
optional positive integer autoCompactTokenLimit. When the authorized pair
holds it, settings.Contract.Config() sends config.model_auto_compact_token_limit,
so create_thread, create_worktree_thread, send_message_to_thread's resume and
the relay's managed start (which creates through bridge.CreateThread) all
carry it. An IF DeepSeek child then compacts below the provider's real input
ceiling instead of stopping without a receipt above about 664k tokens.

A value that is not a positive integer is refused while the policy is read,
through the existing PolicyError path: no new refusal name. A policy that
declares no limit is unchanged, and the field is absent from the policy
description, which is the authorization surface.

The host has no field that reports the value back, so the resume observation
never compares it: its silence withholds nothing and fails no receipt. The
receipt records it under requested and unobservable, and never under verified.

docs/role-execution-policy.md and the crw-run bridge reference state the field,
the recommended 550000 and the probe behind it.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for security reviews. Please try again later.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-07T01:19:10.235164Z 46b0c90 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Devin Review found 1 potential issue.

Devin Review

Comment thread internal/bridge/create.go

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 46b0c908d1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/crw/skills/crw-run/references/bridge.md Outdated
… and resume

The execution policy's pair may declare an optional positive integer
autoCompactTokenLimit. The bridge sends it as config.model_auto_compact_token_limit on
thread/start and thread/resume, and the relay resolves it from the policy for every resume
it makes from a task's record, including a settings-free resume. A host whose reported
window is larger than what the provider behind a model accepts then compacts before the
provider cuts the stream.

A value that is not a positive integer is refused while the policy is read, through the
existing policy error path; absent means today's behaviour and no new refusal name appears.
The host never reports the value back, so the resume observation never compares it, its
silence withholds nothing, and the receipt records it as requested and unobservable.

Fixes the red Devin finding (relay resumes dropped the limit) and the Codex P2 (the
crw-run bridge reference now states the recommended value and its evidence).
@thisisjun786

Copy link
Copy Markdown
Owner Author

CI on head 9769aeba5907d4c1959a3aad526cfedcfc9db130

Workflow run 37558319460, attempt 1, event pull_request. Every job succeeded on this one head:

Job Result
validate pass
secrets pass
go-product (lint) pass
go-product (test-1) pass
go-product (test-2) pass
go-product (test-3) pass
go-product (test-4) pass
go-product (test-rest) pass
go-product (dist) pass
gui pass
skill-scripts-node pass
dev-gate pass

The test-* and test-rest legs report testSkipped: true, which is the light-mode design on pull_request: those legs skip their tests by design and the hosted full suite is the dev-gate path. Locally, go test -count=1 ./internal/bridge/... ./internal/relay/managed/ ./internal/relay/adapter/ is green on this same head.

Reviews. Devin Review completed once on 46b0c908d with one red finding; it is fixed on this head and the thread is resolved with code evidence. The GitHub Codex code review completed once on 46b0c908d with one P2; it is fixed on this head and the thread is resolved. The Codex security review was skipped by its usage limit ("You have reached your Codex usage limits for security reviews"), so it is not awaited.

Base. origin/dev is 9f8eb9bd2, later than this head's base; git merge-tree --write-tree HEAD origin/dev reports no conflict, and the merged tree builds, vets and passes the changed packages' tests.

The audit of this pull request found that a settings-free resume's refusal said
"nothing was transmitted" or "only the MCP profile's overrides were transmitted".
That resume can now also carry the pair's auto-compaction limit, which is neither
a pair nor an MCP override, so the sentence under-reported what went out and sent a
reader looking for the wrong cause.

verifyResume now names what was actually sent, from a flag the resume sets when it
adds the limit. The two wordings that were already there are unchanged, so the
captured adapter goldens are unaffected: their policy declares no limit.
@thisisjun786 thisisjun786 added the crw-lane Merge lane holds this pull request: CI runs every test leg (temporary light PR CI mode, CRW-790) label Oct 7, 2026
plugin.json version regenerated with crw-dev ci plugin --record-version on the merged tree (0.4.0+3c7e25c508a3); no other conflict.
@thisisjun786 thisisjun786 removed the crw-lane Merge lane holds this pull request: CI runs every test leg (temporary light PR CI mode, CRW-790) label Oct 7, 2026
crw-934 and others added 8 commits October 7, 2026 22:58
The relay's resume resolved the pair's model_auto_compact_token_limit from the
policy and put it in the config, but only set a transient flag on the copied
settings: the host never reports the value back, so the stored and replayed
receipt lost both it and the fact that it went out. The receipt of every relay
resume that sends the limit now records it the way the bridge records a setting
the host cannot confirm - the value under requested, the field under
unobservable, and nothing under verified - and a resume that carried no limit
records no such observation.

The reference called 550000 a measured value. The probe measured the mechanism
at a limit of 4000; 550000 is the recommended margin below the observed
provider limit, and both documents now say so.
…pact-limit

# Conflicts:
#	plugins/crw/.codex-plugin/plugin.json
A record need not cite its own role: the sender's gate resolves the role the
task is bound to from the store binding and authorizes the send against that
role's pair. Reading the role only from the record's own citedRole found none
and sent no auto-compaction limit on a delivery the gate had accepted, which is
the case the limit exists to cover.

The role the gate confirmed now travels with the send, and the limit is read
from it before the record's citation. The registry exposes it from the same read
that authorized the send, the two settings loaders report it, the MCP copy keeps
it, and the supervisor's settings fence compares it so a re-read that lands on a
different binding still cancels the claim.
The policy editor's setRolePairs rebuilds a role's whole pairs list from the
model and effort of each pair the change states. It did not know the pair's
autoCompactTokenLimit, so a change that retained a limited pair silently
published the same pair without its limit, and a legacy single declaration that
carried the limit could not be moved to the pairs form at all: the editor left
the limit beside the list, where the parser refuses it.

The limit now travels with the pair. A retained pair keeps the limit the
document already declared for its model and effort, a legacy declaration's limit
follows its one pair into the list, and the read projection shows the limit on
the pair that declares it. A pair without a limit is still written without the
key.
The policy editor's pair type collapsed a supplied autoCompactTokenLimit into
an int64, so a request that stated 0 or null was read as "no limit": the check
approved a document whose pair silently kept an older limit, or dropped the
invalid value. A fraction or a string failed the decode instead, which the API
answers as a bad request rather than as the refused change the rule is.

The pair now keeps the value exactly as the request spelled it, and the editor
writes that spelling into the candidate document, where the same parser rule the
policy file is held to refuses it. A pair that declares no limit is still
written without the key.

Both documents now also state the issue's measured application restriction: a
resume does not install the value on a thread the host already holds; it applies
at creation or on the next load, so an accepted resume receipt records that the
limit was sent, not that it applies.
crw manage child-resume resumes a recorded child from the relay's settings-show
answer, and that record cannot hold the auto-compaction limit (the host never
reports it back). The command therefore reloaded a capped child with the host's
own threshold and reported success, which is the failure the limit exists to
prevent.

The resume now resolves the limit from the execution policy by the pair the
record states, using the role the relay's gate confirmed the task is bound to
(falling back to the record's own citation), and puts it in the thread/resume
config. A record that cites an exception, a role the policy does not declare, or
a pair that declares no limit sends no key, exactly as the relay's own resume
does. A configured policy that cannot be read is a refusal rather than a silent
omission.

Both documents now say which read wins: the bound role the gate confirmed, with
the record's citation as the fallback.
crw manage child-resume resolved the pair's auto-compaction limit from
manage.bridge.execution_policy, but the gate that authorized the record is the
settings-show subprocess, which reads CODEX_THREAD_BRIDGE_EXECUTION_POLICY from
the environment it inherits. Under the supported environment-only configuration
the gate accepted a capped pair and the command then sent no limit, so a capped
child was reloaded under the host's own threshold while the command reported
success. The environment value is now authoritative, with the configuration
value as the fallback.

The report also says what went out: the value the resume sent, and that the host
cannot be asked about it. The host never reports the limit back, so without that
an operator cannot tell a capped reload from an uncapped one.

The execution-policy guide no longer tells operators that a pairs entry must be
exactly {model, reasoningEffort}, which the new field had made wrong.
plugin.json version regenerated with crw-dev ci plugin --record-version on the merged tree (0.4.0+c1e4515b60ba); no other conflict.
@thisisjun786
thisisjun786 merged commit a595f6d into dev Oct 8, 2026
12 checks passed
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