Repository navigation
Give each section one conversation and compact it in place - #166
Closed
vinniefalco wants to merge 1 commit into
Closed
vinniefalco wants to merge 1 commit into
vinniefalco wants to merge 1 commit into
Conversation
Shrink the public API of the Engine by removing public items and narrowing others to `pub(crate)`. The change spans the parser, types, vfs, lua, and engine crates and the `runvil` facade. `crates/runvil/public-api.txt` loses 52 lines to match. - Remove `Prompt::strip_h1_prose`, the `description_text` field, `RunContext::cancel_handle`, `RunContext::current_model`, `RunContext::depth`, `RunContext::name`, `ModelCatalog::is_empty`, `ToolIdError::field`, `ModeHandle::mode`, `SectionVm::global_json`, and `runvil_version`. - Remove the `Depth` and `GlobKind` enums and the forms that take them: `mkdir_with` and `remove_with` on `Access` and `VfsAccess`, and `glob_with` on `VfsAccess`. Remove `Access::read_range`, `Access::read_range_numbered`, and the helper `with_line_range`. - Stop exporting `RunUid`, `GlobalNameErrorKind`, and `OpSink` from the `runvil` facade. `RunContext::uid` and `GlobalNameError::kind` become `pub(crate)`, because they return types that the facade no longer exports. - Narrow 48 items in total to `pub(crate)`, for example `RunLimits::tool_iterations`, `SourceLocation::new`, `FailureRecord::new`, `RealBackend::with_read_only`, `Compactor`, and `SharedSource`. Items that only tests call also get `#[cfg(test)]`. - `split_h1` now returns `Vec<Block>` and no longer builds the description string. The `Debug` output of `RunContext` no longer shows `depth`. - The tests for `read_range`, `read_range_numbered`, `remove_with`, `mkdir_with`, and `runvil_version` are deleted with the code, and nothing replaces them. The `glob_with` test now calls `glob_kind`, and the parser tests that read `description_text` now check `h1_blocks`.
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR gives each section exactly one conversation and makes compaction the one place its history is rewritten. It follows the plans in
vibe/2026-10-21-1-section-messages.mdandvibe/2026-10-22-1-section-messages-debt-removal.md.The Engine now creates
messageswith every section VM, andmodels.loop(compactor?)always runs over it. The section's tool list is rendered once, at the first loop round, and every later round sends the same schemas, which keeps the provider's prompt cache. Records can carrymetathat the model never sees. Compaction rewritesmessagesin place, on overflow or throughmodels.compact. A debt pass at the end removed leftovers, made an uncaught directcompactors.failend the run as context exhaustion, and gave the Workshop chat agent a summarizing compactor.One conversation per section
What changes
SectionVmowns oneMessageList, created with the VM and served asmessagesthrough the_Gguard's slot table, the wayargvandproseare served. Every section entry gets a fresh, empty list: walked sections,jumptargets,callchains, task chains, fanout arms, and the H1 pass. Arequired library sees the requiring section's list.models.loop(compactor?)andhandle:loop(compactor?)take only the compactor and read the list through the guard. An omitted compactor meanscompactors.fail. The loop checks the receiver, then the arity, then the compactor's type, then whether a loop is running.a loop or a compactor is already running in this section; ask a side question with models.infer(prompt).models.inferandtools.callstay allowed inside handlers and compactors.Changes callers will notice
messages.newreads nil like any other non-integer key, with no guiding error. Assigning the global, assigning by index, and writing to a record view each raise their own refusal.models.loop(msgs, c)becomesmodels.loop(c), and the four argument refusals name(compactor?).NOT_A_LISTis gone. A chat request whosemessagesfield is not a list is a malformed yield, because only the Engine builds that field.prompts/research-person.mdwrite tomessagesdirectly.The rendered tool list
What changes
models.looporhandle:loopround that builds its tools attaches aRenderedTools(the schemas, plus each name'sOption<ToolId>) tomessages, before the projection and the precheck. Every later round reuses it, including rounds on another model and the retry after a compaction.tools.offer,packs.offer, andtools.offer_localcheckMessageList::rendered()before anything else, so every offer after the render raises, even one identical to an earlier offer. A round refused before the build, such as one over an empty list or with no model, renders nothing.DispatchTargetis removed.chain.advertisedholdsOption<ToolId>, the valueToolCallEvent::with_tooltakes.Changes callers will notice
tools.offer: the section's tool list is fixed at its first models.loop round; make every offer before the first loop, or offer in another section. Work that offered between two loops now uses two sections.TOOL_SCOPE_VALIDATION_*pair fires once per section instead of once per round.Record metadata
metaargument, as inassistant(content, nil, meta), andappendandreplaceaccept ametafield.messages[i].metareads it, and a view's JSON form includes it.metais any JSON value of at most 8192 bytes as compact JSON (MAX_RECORD_META_BYTES), andjson.nullmeans none. The projection builds wire messages from the four contract fields only, sometanever reaches the model. Tools see it when a view is passed as arguments.Compaction
What changes
compactors.fail(reason)takes any reason string. The reason is aStringin both the Lua and the Engine error types, and the message readscontext exhausted: {detail}, where the detail keeps today's text forprecheckandproviderand is the reason itself otherwise.models.compact(compactor, reason), the Engine snapshots the records and calls the compactor with thecompactingflag set. The compactor editsmessagesin place.max_tool_iterations. A retry that overflows again restores and raisescontext_exhausted.compactors.clearempties the list and leaves the rendered tool list attached.replaceandcompactors.clearwork only while a compactor runs.Changes callers will notice
COMPACTOR_RETURNEDis gone.replaceoutside a compactor raisesreplace works only inside a compactor; rewrite history with models.compact(compactor, reason), and the assignment, index, view, and late-system refusals now point tomodels.compact.context_exhaustedwith a caller's reason ends the run asContextExhaustedwith that reason in its message.Debt removal
OverflowReason::ALLand the unusedstd::error::Errorimpl onOverflowReasonare removed, along with two stale doc texts.compactors.fail(reason)in a block now ends the run asContextExhausted, where it used to end asLua(orRequirementsUnmetin H1).map_runtime_error_amongclassifies the typed raise next to the existing quota case, and only that variant.models.loop. When a turn overflows, it summarizes every record before the last two user turns with onemodels.infercall and retries. The summary input is capped at 1,500 characters per record and 48,000 in total, sized for the agent'smin_context: 32768. With fewer than two user turns there is nothing to summarize, and that turn fails as before. Before this change, every turn after the first overflow failed silently.Commits
df3f01c78Refuse a loop started while another runs in a section. Addsloop_runningand theLOOP_RUNNINGrefusal.b1950987bGive each section VM its own messages list. Injects the list, removes the constructor and the list argument, and migrates every prompt and test.f40914cd5Freeze a section's tool list at its first loop round. AddsRenderedToolsand the offer gate, and removesDispatchTarget.4a516fdc9Let records hold metadata that never reaches the model.c952b5c78Let compactors.fail raise with any reason.511386d85Compact a section's messages in place on overflow or demand. Adds the snapshot, the validation,models.compact, andcompactors.clear.4ca36d6f6Allow replace and compactors.clear only inside a compactor.182167935Align prose docs with the section's one conversation.1e808b41fClose plan: section-messages79305a90fRemove OverflowReason's ALL list, Error impl, and stale docs.5a426a4efMap uncaught direct compactors.fail to context exhaustion.992c6fdffSummarize older chat turns when a turn overflows.7b6ababb5Close plan: section-messages-debt-removalTesting
Each commit passed a review and its gates locally on Windows before the next one started. At the final code state these all passed:
-D warnings: the workspace,runvil,harness, andrunvil-engineandworkshop-serverwith private itemscargo +nightly-2026-09-05 xtask api --check, withpublic-api.txtunchangedcargo test -p build-xtask(257 passing) and the 1.95 floor build of the Engine cratesThe plan's completeness searches for
messages.new,NOT_A_LIST,DispatchTarget, andCOMPACTOR_RETURNEDprint nothing. The new regression tests failed before their fixes, including a Workshop chat gate that runs the shippedchat.mdthrough two overflowed turns. Onegateway-loggingtiming test failed once during the local runs and passed when rerun. This PR changes no gateway code.The two commits now on
master(#161 and #165) touch only gateway code and share no file with this PR.Known gaps
pcallcould leave the busy flags set for the rest of the section. This has not been demonstrated.Model turn failedframe before the reply.Related
runvil-docsrepository and are not part of this PR:c0f04c1rewrites the guide for the section's conversation, ande4937eddocuments the directcompactors.failrun error and the chat agent's compactor.