Skip to content

Own goal operations and modal results on their declared types - #1398

Merged
trissim merged 2 commits into
mainfrom
fix/goal-surface-contract
Nov 1, 2026
Merged

trissim merged 2 commits into
mainfrom
fix/goal-surface-contract

Conversation

@trissim

@trissim trissim commented Nov 1, 2026 •

Copy link
Copy Markdown

Summary

2 coherent commits, 4 production files, +39/-31. Three goal operations are declared on the connection owner; two existing default methods leave NoAgentConnection for that parent; one unused modal-dismissal text result and both UI ControlledExecution type checks are deleted. No new class, adapter, cache, codec, registry, test controller or compatibility entrypoint.

Paired Core API: OpenHCSDev/agent-comms#3844, 4fa21cafc5aec01f10201c49b16d13100446530a. Land Core first. The new Toad head consumes the new execution API and must not be installed against old Core. Both changes are the same goal-control overhaul, not independent acceptance claims.

The previously retained goal-control target had three findings: missing connection history/update contracts and _modal inferred as permanently None. The current target check reports 0 errors, rc0, one file checked. This is not a whole-project zero-debt claim or a new application qualification.

Connection owns its goal contract

Before:

AgentConnection: no goal-operation contract
  NoAgentConnection: empty history + bare ValueError update
  AgentBase
    acp.Agent: actual history/edit/update RPCs
GoalSession: holds AgentConnection, calls undeclared operations

After:

AgentConnection: typed history/edit/update; shared empty/unsupported defaults
  NoAgentConnection: inherits those defaults (two local methods deleted)
  AgentBase
    acp.Agent: keeps the real RPC overrides
GoalSession: uses the declared connection contract directly

GoalId is the existing identity type for history reads. GoalSession.write carries its awaited result type rather than abandoning the contract inside an untyped method. EditInteraction.apply is now checked too.

The shared unsupported reason has one declaration on AgentConnection; both writes raise the existing GoalRefused, which already composes expected refusal with ValueError. This retains the old unsupported reason while using the existing UI refusal mechanism. The ACP request/decode bodies are unchanged.

Counterexample that shaped the fix: tests/model/test_session_observation_cancellation.py:17 defines a read-only ReadAgent(AgentBase). Adding abstract goal stubs would make that existing fixture uninstantiable. Concrete defaults on the actual connection parent preserve the family without three fake implementations or a mock exemption. No test was rewritten to tolerate the wrong structure.

Modal result follows its actual reader

GoalSession keeps a truthful ReferenceType[ModalScreen[None]] | None, and its modal/present contracts say the same thing. Both current modal members support this contract: GoalDetails derives SourceModal/DismissibleScreen[None]; GoalEdit now declares DismissibleScreen[None].

Deletion certificate, bounded to the owned Toad source/tests/tools callers:

  • At the initial 66f47e41a checkpoint, GoalEdit constructor sites are goal_interaction.py:116 and test_goal_owner_views.py:160,221. The toggle cleanup only shifts that unchanged production call; other native/Textual references only inspect the already-open editor.
  • Production GoalSession.present uses App.push_screen(modal) with neither callback nor dismissal wait. The two direct test pushes likewise only await mounting. Textual's actual push_screen/dismiss result contract was traced, not guessed from its name.
  • Saved text is already read by await self.on_save(text.strip()) before dismissal. That callback, exception handling, saving guard, draft retention and all finally work remain unchanged.
  • No owned reader consumes the redundant self.dismiss(text.strip()) result. It becomes self.dismiss(); no saved text is removed from the real save path.
  • External/dynamic extension callers are outside this closed owned-call-site certificate; no claim of an arbitrary plugin API census.

There is no generic modal manager or invariant-generic cast hiding an incompatible result.

Execution owns toggle behavior (53444/53446)

Before, ToggleInteraction.visible/apply asked twice whether an already-held GoalExecution was ControlledExecution. This was old IMPL-3/MEMB-2: fact=availability and action; at=old goal_interaction.py:84,93; derive=base-method/subclass; by=Core GoalExecution/ControlledExecution. Paper1 §1 and Theorems4.6/4.7 apply; a UI switch was maintaining a family fact outside its declaration.

The paired Core change puts can_toggle and apply_toggle on those existing owners. The default execution calls unavailable feedback only; controlled execution awaits the existing goal-change receiver with its existing action. Default Pause caption moves unchanged to GoalExecution; Retry/Resume and action properties stay with their existing leaves. This UI now asks/invokes the family without recovering its type. Its initial/fallback caption derives from Core, deleting the second Pause spelling. The existing UI read-only visibility gate stays.

Both unavailable feedback sites now use existing GoalSession's concrete unavailable method; there is no new manager or fixture method. No wire, state, generation, input, permission, scheduling or constructor/base change.

Paired proof artifacts: toggle-{before,after}-map.json, toggle-production-consumers.txt, toggle-source-proof-v2.json in the same scratch directory. The relevant Core/Toad source corpus has28modules/544raw/542canonical classes before and after, zero parse omissions, two local declarations OPEN (Responses and Client). Dynamic/foreign/mutation cases remain OPEN. Exactly3paired files changed; all existing class bases and other annotated fields match.88existing method/function bodies are unchanged; four Core methods and one shared UI method are added. The caption declaration moved AST-identically. Core rebased onto5f8c3716: all28mapped input hashes remain identical (toggle-rebased-inputs.json), so no compiler/test rerun was used as reassurance. Authored exact patches, not automatic NRA movement/effect proof.

After whole-production grep, ControlledExecution occurs only in its Core declaration and three base declarations: zero Toad references, zero UI type checks. Targeted mypy against TWO explicit owned source roots passes0/rc0, one source file checked; no application source overlay/import run. The first extension checker failed before mypy on its mistaken expectation for NRA's nested-function key; that original log remains toggle-source-proof.log. Corrected v2 uses the actual change key; production was unchanged.

Core#3844 authors eight parameterized dispatch/default regression examples, compiled but UNRUN. No extra pytest/native/frontend/install was launched. Necessary future combined cases are that new Core execution test and the existing Toad goal-control/readonly paths. Native09 is not transferred to this new pair.

Original connection/modal source proof (66f47e4)

Retained artifacts: /home/ts/.cache/agent-scratch/pi-shell-colleague/retry-ui/.

  • Before: goal-contract-before-map.json, revision 2b3f6ba4440f34893713d7858730142d3a0bf1a0, 38 modules, 395 raw / 392 canonical classes.
  • After: goal-contract-after-map.json, same four-file working delta plus the two newly used Core refusal declaration modules: 40 modules, 442 raw / 439 canonical classes. The extra 47 classes are expanded dependency coverage, not production class growth. Exactly four mapped inputs changed; 34 original mapped inputs are hash-identical.
  • Existing NRA parse_python_module_roots, ModuleSyntaxIndex, ClassFamilyIndex and build_source_index own the source map, class census, lexical scopes and declaration/write/call/import events. Exact authored patches implement this bounded change; no automatic promotion/native-effect proof is claimed.
  • 0 parse omissions. Three function-local classes remain unprojected OPEN: OwnerReconnecting, ControlledProvider.serving.Handler, and RetainAction. Runtime imports, mutation, foreign factories and unknown extension callers remain OPEN, not zero debt.
  • goal-contract-proof-v2.json: all four files compile; 115 common method bodies are AST-identical. The weakref constructor annotation changes its AST but its executable bytecode/constants are identical. The empty-history body moved unchanged; the unsupported reason is identical. Among common methods, only the editor's unused dismissal argument changes executable work. Separately, the moved unsupported write now raises the existing GoalRefused instead of bare ValueError, as documented above.
  • Targeted mypy, existing read-only dependency, exact arguments: --follow-imports=silent --ignore-missing-imports --no-incremental src/toad/goal_interaction.py; success, rc0, one source file checked.
  • First proof attempt stopped at an incorrect expected AST-body-change set: it omitted the local weakref annotation. That failed log remains goal-contract-proof.log; no type-check result was produced from it. The corrected source proof is a distinct artifact.
  • git diff --check passes. Current open PR files were read before editing and refreshed before publication. Errors confirmed Keep queue actions on captured sources and original recipients #1389's shared connection files have no competing goal-contract hunk; its submission/queue repair remains separate. P918: relocate current ToAD consumers to Core nominal owners #1330's broad import sorting is not a competing implementation.

Scope and remaining evidence

No pytest, local NRA gate, native/provider/frontend job or install was run for this overhaul. The initial typing proof followed commander53319; the added owner/consumer cleanup follows53444/53446 and has separate paired source proof above. The prior Core14/Toad12 and brace selections were not repeated. The accepted native09 Retry/refusal/DM result remains evidence for its original installed pair, not this unmerged head. New physical editor/button/warm-history/mobile acceptance is not claimed.

At the next independently necessary integration selection, the relevant existing behavior paths are tests/textual/test_goal_owner_views.py, tests/native/test_native_goal_objective_edit.py and the read-only observation construction in tests/model/test_session_observation_cancellation.py. This is a risk handoff, not a request for per-head reassurance runs.

Assumed: owned goal-modal callers define the supported dismissal-result domain; the existing Core GoalRefused contract remains the expected-refusal envelope; the integration owner retains merge/install/live qualification. No store, generation, input, permission or scheduling data changes.

put the methods on the type we already hold
delete the duplicate defaults
the connection can now remember its own job description

Copilot AI balanced review requested due to automatic review settings November 1, 2026 20:14

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota. Try requesting a review at a lower effort level.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@trissim
trissim merged commit d5c905f into main Nov 1, 2026
2 of 4 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.

2 participants