One door: promptforge-api as the single public promptforge crate - #37
Closed
vinniefalco wants to merge 20 commits into
Closed
One door: promptforge-api as the single public promptforge crate#37vinniefalco wants to merge 20 commits into
vinniefalco wants to merge 20 commits into
Conversation
Every agent session now runs a Markdown prompt on the unified runtime; the standalone Lua program path is retired. Discovery lists Markdown file stems, a same-named directory file still shadows the embedded built-in chat, and a leftover Lua program is ignored rather than served. Launch, input recording, and run-error handling no longer branch on program kind, and the model-catalog builder and tool wiring that only the retired path consumed are gone. - `AgentSource` keeps a single `Markdown` variant: every session, directory or built-in, is a Markdown prompt on the unified runtime. - `AgentRunError` replaces the retired runtime's error type session-locally: cancellation maps to `Interrupted`, every other failure to an operator-facing message. - `discover_agents` lists `.md` stems only; a `.lua` file is never an agent. - `agent_source` lets a directory `chat.md` shadow the built-in while a directory `chat.lua` shadows nothing. - `accept_input` always records consumer-side; the producer-side branch for relaunched Lua history is gone. - `build_model_catalog` and `FALLBACK_CONTEXT` are deleted; the session no longer parses gateway catalog JSON into model descriptors. - `launch_lua` is deleted, and the supervisor no longer builds a `UserInputTool` catalog for the run. Design: removes dispatch-on-tag @ crates/workshop-sessions/src/agents/session.rs::AgentSource Design: removes stringly-typed @ crates/workshop-sessions/src/agents/session.rs::build_model_catalog deps: Option<Vec<serde_json::Value>> boundary: wire Design: removes speculative-abstraction @ crates/workshop-sessions/src/agents/supervisor/effects.rs::RunFactory Plan: vibe/2026-09-12-5-one-door-promptforge-api.md
The session carried two constructs for one job, suspending an agent program until its operator answers: a host-primitive tool that served only the retired standalone script path, and the input broker behind the script-side input call. This change deletes the tool along with its constructor, its trait implementation, and its unit tests, and removes its re-exports from both crate roots. The documentation now presents the broker as the single input path. The wait registry, the drop guard, and the broker itself are unchanged. - `crates/workshop-sessions/src/input/tool.rs::UserInputTool` - Deletes the struct, its constructor, and its Tool trait implementation; the session's input broker is now the single construct that opens a wait, announces it, and suspends until the operator answers. - `crates/workshop-sessions/src/input/tests.rs` - Removes the six unit tests that exercised the deleted tool's contract, including byte-exact text resumption and cancel framing; the registry-level tests such as reconnect resend remain. - `crates/workshop-sessions/src/lib.rs` - Drops the deleted tool from the crate-root re-exports, and the server crate root does the same, so the type leaves the public API of both crates. Design: removes parallel-abstraction @ crates/workshop-sessions/src/input/tool.rs::UserInputTool boundary: pub Plan: vibe/2026-09-12-5-one-door-promptforge-api.md
The standalone Lua agent-program executor is removed so a single runtime remains for both document prompts and agent loops. The integrator facade drops its second entry point and now exposes only the document-prompt pipeline. The server's socket integration tests drive their fixture agents as Markdown prompts on the unified runtime instead of Lua programs on the deleted executor. A new drop guard on the run scheduler aborts in-flight leaf tasks when a host tears a run down mid-suspension, so closing a session no longer strands a pending input wait. - `crates/promptforge-agent`: the entire crate is deleted - `run_agent`, `run_agent_with_client`, `AgentConfig`, `AgentError`, and their tests - ending the sibling executor over the shared substrate; agents now run as Markdown prompts. - `crates/promptforge/src/lib.rs`: the `agent` re-export module is removed, leaving `pipeline` as the facade's only entry point; the crate's manifest and README drop the second dependency and entry point to match. - `crates/workshop-server/tests/it/chat_gate.rs::AgentError`: a test-local error enum mirrors the supervisor's run-outcome type, replacing the import from the deleted crate; its `Program` variant carries only the rendered message, dropping the boxed source. - `impl Drop for Scheduler<'_>`: dropping the driver future aborts every handle in `io_tasks`, applying the cancellation path's abort on the drop path; a detached task would otherwise strand a broker wait or gateway round and never emit `input_cancelled`. - `crates/workshop-server/tests/it/agents.rs::ECHO_MD`: the echo fixture is rewritten from a Lua program to a Markdown prompt whose loop calls `user_input()` directly and runs each chat round through `models.loop`. - `crates/workshop-server/tests/it/agents/turns.rs`: a turn's durable record is now input, thinking, reply; the direct `user_input` call is not a tool call, so no `tool_call_update` event exists and the log indices shift down by one. - `crates/promptforge/tests/paths.rs::pipeline_paths_resolve`: the compile-level path proof no longer covers an agent entry point, because none remains. Design: removes parallel-abstraction @ crates/promptforge-agent Design: removes facade @ crates/promptforge/src/lib.rs::agent boundary: pub Design: new parallel-abstraction @ crates/workshop-server/tests/it/chat_gate.rs::AgentError Plan: vibe/2026-09-12-5-one-door-promptforge-api.md
The agent documentation now teaches an agent as an ordinary Markdown prompt document discovered from a directory, replacing the standalone Lua program model. The built-in chat is presented as an embedded prompt that a same-named directory file shadows, and the full-loop walkthrough keeps the conversation in a retained message list instead of rebuilding it from the event log on every turn. A new crate README states the discovery, launch, and session contract in the same terms. - `crates/workshop-sessions/README.md` is new and defines the subsystem contract: discovery lists `.md` file stems under `agents.path`, the embedded `chat` prompt guarantees a working agent with no directory at all, and launch goes through `Prompt::parse` and `promptforge_core::run`. - `guide/src/agent/01-agent-programs.md` redefines the agent as a prompt document whose only extras are the session-installed `user_input()` and `ui()`; the smallest example is now frontmatter, one section, and one Lua block. - `guide/src/agent/10-the-full-loop.md` rewrites the full program around a retained `messages.new()` history: `history:user(text)` appends operator input, `models.loop(models.get(selected), history)` runs each turn under `pcall`, and the model is re-read from `ui().selected_model` every turn. - `guide/promptforge-agent-guide.md` and `guide/promptforge-workshop-guide.md` carry the same rewrites into the single-file guide mirrors. - `guide/src/introduction.md`, `guide/src/workshop/01-application.md`, and `guide/src/workshop/06-chat.md` swap the remaining `.lua` references for `.md` in the agent-set pointer, the `agents.path` config description, and the chat chapter. - `call`, `fanout`, and `jump` absence claims are dropped along with the Lua-coroutine execution description; the rewritten chapters no longer enumerate host calls that do not exist. Plan: vibe/2026-09-12-5-one-door-promptforge-api.md
Renames the shared host-support crate to its final shared-vocabulary name, ahead of later work that sinks model and tool vocabulary into it. Every dependent manifest, import path, doc comment, and lockfile entry moves to the new name while the crate's own sources move unchanged. No runtime behavior changes. - `crates/shared-promptforge-api/Cargo.toml`: the package takes the new name with version, edition, and license still workspace-inherited; its dependency set is unchanged. - `Cargo.toml`: the workspace dependency entry now points at the renamed crate path, and all eight dependent manifests follow it. - `crates/promptforge-core/src/lib.rs`: the public re-export of `CancelHandle` now sources from the renamed crate, so existing consumer paths keep working unchanged. - `crates/shared-promptforge-api/src/lib.rs`: moved with 100% similarity; no item was added, removed, or re-scoped in the rename. Plan: vibe/2026-09-12-5-one-door-promptforge-api.md
The model identity, catalog, descriptor, and thinking-mode vocabulary moves out of the model client into the shared API crate, joined by the streaming delta type hosts name in delta callbacks, so every crate and host can name these types without depending on transport machinery. The model client keeps its transport, binding, and completion types and re-exports the moved symbols through their historical paths, so existing imports keep resolving. Because the catalog now lives in another crate, its constraint-filtering helper becomes an extension trait in the model client, where the bind-options type still lives. The validation tests move with the types. - `crates/shared-promptforge-api/src/models.rs` becomes the canonical home for the identity, catalog, descriptor, and thinking-mode types, moved with their validating constructors and unit tests. - `crates/shared-promptforge-api/src/wire.rs` holds the streaming delta enum, the one wire type a host names in its delta callback. - `crates/promptforge-model-client/src/model.rs::ModelCatalogFiltered` is a new doc-hidden extension trait carrying the catalog filtering helper, since the catalog is now foreign while the bind options stay in the model client. - `crates/promptforge-model-client/src/model.rs` and `crates/promptforge-model-client/src/client.rs` re-export the sunk symbols so the historical import paths keep resolving. - `crates/promptforge-model-client/src/model/ids.rs` is deleted; its types and tests now live in the shared crate. Design: replaces encapsulated-invariant @ crates/shared-promptforge-api/src/models.rs::ModelCatalog was: crates/promptforge-model-client/src/model.rs::ModelCatalog Design: replaces encapsulated-invariant @ crates/shared-promptforge-api/src/models.rs::ModelId was: crates/promptforge-model-client/src/model/ids.rs::ModelId Design: replaces value-object @ crates/shared-promptforge-api/src/models.rs::ModelDescriptor was: crates/promptforge-model-client/src/model/options.rs::ModelDescriptor Design: new speculative-abstraction @ crates/promptforge-model-client/src/model.rs::ModelCatalogFiltered Design: extends facade @ crates/promptforge-model-client/src/model.rs Design: extends facade @ crates/promptforge-model-client/src/client.rs Plan: vibe/2026-09-12-5-one-door-promptforge-api.md
The tool contract vocabulary moves out of its standalone crate and into the shared API crate as a public module, leaving a single vocabulary home that every other crate in the workspace may depend on. Each former dependent now imports the contract types from that module, and the existing compatibility re-export keeps established use paths working unchanged. The deprecated free-function alias for guard wrapping is removed, so the method form is the only spelling. The retired crate's manifest, documentation, and workspace entry are deleted. - `crates/shared-promptforge-api/src/tools.rs` - The full contract vocabulary (trait, catalog, identity, output, and error types with their kinds) becomes a public module of the shared crate; the content is the retired crate's source with documentation paths rewritten, and the crate manifest gains the async-trait edge the trait needs. - `crates/promptforge-core/src/tools.rs` - The compatibility re-export is kept but now sources the vocabulary from the shared crate's module, so existing core paths resolve to the identical trait and types. - `crates/promptforge-core/src/tools/tests.rs` - The re-export identity tests now pin against the shared crate's path; a lookalike re-export would fail to compile rather than pass silently. - `crates/promptforge-tools/Cargo.toml` - The standalone crate is deleted with its manifest, docs, and workspace dependency entry; every former dependent now depends on the shared crate instead. - `crates/shared-promptforge-api/src/untrusted.rs` - The deprecated `wrap` alias is removed; `GuardNonce::wrap` remains the only spelling. Design: replaces encapsulated-invariant @ crates/shared-promptforge-api/src/tools/registry.rs::ToolCatalog was: crates/promptforge-tools/src/registry.rs::ToolCatalog Plan: vibe/2026-09-12-5-one-door-promptforge-api.md
The runtime crate takes the api name and becomes the one library door for outside consumers, absorbing the integrator-facing package metadata from the retired facade, which is deleted outright with its path-resolution test. Every reference across dependents, tests, benches, and guides follows the rename, including the Lua shim chunk name whose verbatim path renders in tracebacks. No runtime behavior changes; the executor test suite moves with the crate. - `crates/promptforge/src/lib.rs` deletes the re-export-only facade crate outright, with its manifest, docs, and compile-level path test; no alias or shim is left behind. - `Cargo.toml` collapses the promptforge and promptforge-core workspace entries into one promptforge-api entry, and the lockfile drops the facade package. - `crates/promptforge-api/Cargo.toml` absorbs the retiring facade's integrator-facing keywords and categories and retitles the description as the PromptForge API. - `crates/promptforge-lua/src/coro.rs` points SHIM_CHUNK_NAME at the promptforge-api path so shim frames in tracebacks keep rendering as clickable file:line references, with the coroutine tests asserting the new spelling. Design: removes facade @ crates/promptforge Plan: vibe/2026-09-12-5-one-door-promptforge-api.md
Running a prompt no longer requires a tool picker or a store handle: capability-free agents pass no picker, and the store handle rides on the run configuration with a stock in-memory default. A capability bind attempted without a picker fails as a binding error naming the missing picker. The crate's public surface narrows to the parser and gateway client modules; hosts name the model, observe, tools, and store vocabulary through the shared crate, with the completion error types re-exported through the client module. - `ResolutionContext` now holds an optional picker, so capability-free agents pass `None` instead of building an empty one. - `RunConfig` gains the store handle as a field defaulting to the stock handle, set through a new `vfs` builder; `run` drops its separate store parameter. - `NoPicker` is the decision source behind a picker-less run: every tool capability decision fails as an unbound bind and the near-duplicate scan is vacuous. - `crates/promptforge-api/src/lib.rs` narrows the public re-exports to `parser` and `client`; the model, observe, store, and tools modules go crate-internal and the root cancel-handle re-export is dropped. - `run` still overlays a defensive memory store when the config's handle lacks the mount, so a run never fails for want of the mount. - `RuntimeResolution` fails a described-model bind without a picker as a binding error whose detail names the missing picker, rather than resolving through an empty catalog. Design: extends parameter-object @ crates/promptforge-api/src/execute/config.rs::RunConfig boundary: pub Design: new strategy @ crates/promptforge-api/src/resolve.rs::RuntimeResolution Design: new surface-growth @ crates/promptforge-api/src/execute/gateway.rs::ResolutionContext boundary: pub Plan: vibe/2026-09-12-5-one-door-promptforge-api.md
The progress subscription now runs over the workshop's own gateway client instead of a helper imported from another crate, so the event stream is authenticated and decoded through one door. Decoding keeps the strict posture the subscriber always had: only blank-line-terminated blocks dispatch, heartbeat comments are skipped, and a block that grows past a hard size bound is refused rather than buffered without limit. A non-success answer such as a rejected token surfaces as a bounded, control-escaped status error rather than a relayed response, and an undecodable event degrades to a single error item without ending the stream. Observable behavior is unchanged. - `subscribe_progress` subscribes to the gateway progress endpoint through the client's own base URL and bearer. Only the wait for the response headers is bounded; the long-lived stream itself carries no deadline, and resubscription is the caller's decision. - `GatewayError::Status` and `GatewayError::Malformed` carry a bounded, control-escaped error body and per-event decode failures, so a hostile body cannot forge log lines or smuggle terminal control sequences into a diagnostic. - `decode` yields an undecodable block as one error item and continues; a mid-stream read failure or a block oversized past the one-megabyte bound yields one error item and ends the stream, and an incomplete trailing block is discarded. - `gateway_progress.rs` drops the `promptforge_model_client::model::subscribe_progress` import; the progress task subscribes through the snapshot's client. Design: new surface-growth @ crates/workshop-gateway/src/gateway.rs::GatewayClient::subscribe_progress boundary: pub instead-of: layer-violation: promptforge_model_client::model::subscribe_progress across the forbidden promptforge edge Design: new surface-growth @ crates/workshop-gateway/src/gateway.rs::GatewayError::Status boundary: pub Design: new surface-growth @ crates/workshop-gateway/src/gateway.rs::GatewayError::Malformed boundary: pub Design: new surface-growth @ crates/workshop-gateway/src/gateway/progress.rs::ProgressEventStream boundary: pub Design: new pure-function @ crates/workshop-gateway/src/gateway/progress.rs::block_end deps: &[u8] Design: new pure-function @ crates/workshop-gateway/src/gateway/progress.rs::parse_event_block deps: &[u8] Plan: vibe/2026-09-12-5-one-door-promptforge-api.md
The gateway snapshot no longer carries a prebuilt agent completion client. Each consumer now builds its own client from the snapshot's base URL and bearer at the moment it needs one, through the public API's client re-exports. This drops the gateway crate's model-client edge, and the launch and relaunch paths still refuse with the same unusable-gateway error when the key or URL cannot build a client. - `agent_client` builds the completion client from one snapshot's base URL and bearer, returning None when the key or URL cannot build a client. Construction moves out of the gateway crate into the sessions crate that consumes it. - `LaunchRefusal::GatewayUnusable` still fires at launch and at supervisor relaunch when the client cannot be built, now computed from the snapshot's URL and key rather than a stored client. - `GatewaySnapshot` drops the `model_client` field and its accessor; a snapshot now carries only the HTTP client, base URL, bearer, generation, and identity. - `promptforge-model-client` leaves the workshop-gateway manifest and lockfile, leaving shared-promptforge-api as that crate's only promptforge edge. Design: replaces pure-function @ crates/workshop-sessions/src/agents.rs::agent_client deps: &str,&str was: crates/workshop-gateway/src/gateway_binding.rs::model_client Plan: vibe/2026-09-12-5-one-door-promptforge-api.md
The session and server crates now reach the runtime through the two surviving library crates alone. The hand-built picker, tool catalog, and store plumbing they used to assemble is deleted because the runtime now supplies stock defaults for each, and the manifests drop the retired dependency edges. - `crates/workshop-sessions/Cargo.toml` drops the model-client, tool-picker, and vfs edges, leaving `promptforge-api` and `shared-promptforge-api` as the crate's only promptforge dependencies; `Cargo.lock` shrinks to match. - `crates/workshop-server/Cargo.toml` makes the same collapse for the shell's test-only dependencies. - `RunFactory` no longer stores a picker or a store handle; the removed field documentation admitted the picker never bound tools, so relaunches now rely on the runtime's stock defaults. - `run_markdown_agent` builds the catalog with `ToolCatalog::default()` and calls `ResolutionContext::new(None, &models, &tools)`, so capability-free agents run with no picker and the default store handle. - `shared_promptforge_api::wire::StreamDelta` and `shared_promptforge_api::models::ModelCatalog` replace the retired model-client imports across the session, supervisor, and test code. - `spawn_restored_chat` and `run_builtin_chat` exercise the new defaults, keeping the server integration gate and the session suite green in the same commit. - `MarkdownRunParts` loses its `vfs` and `picker` fields; the supervisor no longer constructs an empty picker or store at any of its relaunch sites. Design: removes speculative-abstraction @ crates/workshop-sessions/src/agents/supervisor/effects.rs::RunFactory Plan: vibe/2026-09-12-5-one-door-promptforge-api.md
Remove the boundary-neutral end-to-end test crate that bridged the gateway and the model client. The crate was dead weight: its live-inference scenario was ignored unless explicitly opted in, and the one-door product boundary leaves no place for a test package that depends on both products at once. The lockfile entry goes with it. - `crates/product-integration-tests/Cargo.toml` - Deleted with the entire crate; it declared a publish-forbidden, dev-dependency-only test package reaching across the product boundary into both `gateway` and `promptforge-model-client`. - `crates/product-integration-tests/tests/gateway_client.rs` - Deletes the 227-line suite: the TestServer harness with graceful shutdown, the fake SSE backend, and the completion helper. - `real_model_client_completes_through_gateway` - Ran unignored and asserted a pong reply through an in-process gateway; it was the crate's only test that ran without an opt-in. - `real_model_client_completes_through_local_gateway` - Was ignored behind `PROMPTFORGE_LIVE_LOCAL` and downloaded llama-server plus a Qwen3 model, so its removal costs no routine coverage. - `Cargo.lock` - Drops the product-integration-tests 0.3.0 package entry with its six declared dependencies. - `product-integration-tests` - Leaves no successor behind: the diff adds no replacement for the deleted wire-contract coverage, and no root manifest edit was needed because the crate never appeared in the workspace dependency list. Plan: vibe/2026-09-12-5-one-door-promptforge-api.md
Rename the workspace automation crate so it carries the same build prefix as the sibling tooling crate. The command alias keeps its existing spelling and now points at the renamed package, so the tidy report and the architecture test suite run exactly as before. Documentation, comments, and the lockfile are updated to match the new name. - `.cargo/config.toml` retargets the `xtask` alias to `run -p build-xtask --`, keeping the command spelling stable while the package name changes. - `crates/build-xtask/Cargo.toml` renames the package to `build-xtask`; the directory move carries `new_crate.rs` across unchanged and `main.rs` and `tidy.rs` with doc-only edits. - `crates/build-xtask/src/tidy.rs` changes only doc text and one test failure message; every architecture check is byte-for-byte intact. - `Cargo.lock` swaps the `xtask` entry for `build-xtask` with an identical dependency set. Design: new shotgun-surgery @ crates/build-xtask Plan: vibe/2026-09-12-5-one-door-promptforge-api.md
Adds a workspace lint that classifies every crate into a product family by package name and rejects dependency edges the product matrix forbids, including any reach past the single public entry crate into the internal substrate crates. The check covers normal, dev, build, and target-specific dependency tables, resolves renamed packages before classification, and reports unreadable or unparseable manifests as violations rather than skipping them. It runs as part of the existing lint harness in the build tooling, and the workspace documentation now states the rule the harness enforces. - `crates/build-xtask/src/product.rs` holds the new check as its own module, keeping `tidy.rs` under the 500-line ceiling. - `boundary_breach` encodes the matrix as a match over family pairs, with the one-door rule applied after the per-family rules. - `product_boundary_violations` binds only workspace members; a crates.io package that happens to carry a product prefix is not checked. - `manifest_dependencies` resolves `package` renames before classification so an aliased dependency cannot evade the matrix. - `workspace_crates` turns unreadable directories, unreadable manifests, unparseable TOML, and missing package names into violations instead of passing them silently. - Nine tests cover the live workspace, injected violations in temporary workspaces, dev, build, and target tables, package renames, and family classification. Design: new value-object @ crates/build-xtask/src/product.rs::Family Design: new pure-function @ crates/build-xtask/src/product.rs::family deps: str Design: new pure-function @ crates/build-xtask/src/product.rs::boundary_breach deps: Family,str Design: new pure-function @ crates/build-xtask/src/product.rs::manifest_dependencies deps: toml::Value Plan: vibe/2026-09-12-5-one-door-promptforge-api.md
Records that the closing verification pass has finished. The full test suite, lint and format checks, dependency metadata inspection, and stale reference sweeps all ran clean, so the work is marked done. Plan: vibe/2026-09-12-5-one-door-promptforge-api.md
Plan: vibe/2026-09-12-5-one-door-promptforge-api.md
The progress subscription moved to the workshop's own gateway client, leaving this copy without a single caller. Delete the function, its SSE decoder helpers, its eight tests, and the README mention so one progress decoder remains. - \crates/workshop-gateway/Cargo.toml\: the \shared-progress\ edge gains the \serde\ feature its progress decoder needs; the feature previously arrived through unification with the deleted code's crate edge. - \utures-util\ and \shared-progress\ leave \promptforge-model-client\ with the deleted code; neither has a remaining use in the crate.
The comment described .lua discovery and no agents on a missing directory; discovery lists .md stems and always offers the embedded chat built-in. Rewrite it to the shipped contract, including the unreadable-chat.md error and the chat.md shadowing rule.
The public doc comment on \ProgressEventStream\ linked to the crate-private \MAX_EVENT_BLOCK\ constant, which rustdoc rejects under -D warnings. Render both mentions as plain code spans instead; the constant stays private.
vinniefalco
force-pushed
the
one-door-promptforge-api
branch
from
September 13, 2026 12:29
e47f138 to
e25c878
Compare
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.
Summary
Outside consumers previously needed seven
promptforge-*crates to embed the executor. This PR establishes exactly two doors and enforces them in the build:promptforge-api(renamed frompromptforge-core) is the onlypromptforge-*crate outside products may depend on.run()is nowrun(&Prompt, &str, ResolutionContext, RunConfig): the store handle moved intoRunConfigwith apromptforge_vfs::empty()default, and theResolutionContexttool picker is optional, so capability-free agents passNone. Only theparserandclientre-export modules survive.shared-promptforge-api(renamed frompromptforge-core-support) carries the cross-product vocabulary: theToolcontract andToolCatalog(the dissolvedpromptforge-toolscrate), the host-facing model vocabulary (ModelId,ModelCatalog,ModelDescriptor,ThinkingMode),StreamDelta, events/metrics,Observer, andCancelHandle. Zero product-crate dependencies..luaagent program path is gone: discovery lists.mdstems underagents.path,chat.mdshadows the embedded built-in, and thepromptforge-agentcrate, thepromptforgefacade,UserInputTool, and the deadproduct-integration-testscrate are all deleted. Guides rewritten to match.workshop-sessionsdepends on exactly the two doors;workshop-gatewayandworkshop-protocolonshared-promptforge-apialone. The gateway progress subscription was reimplemented over the workshop's ownGatewayClient(dropping thepromptforge-model-clientedge), and agent model-client construction moved intoworkshop-sessions.xtask(renamedbuild-xtask) gains aproduct_boundary_violationstidy check codifying the full product-boundary matrix across all dependency kinds, run bycargo test -p build-xtaskon every run, with injected-violation tests proving it fails when it should.Notable fixes along the way
user_inputwaits (the supervisor's close detached leaf tasks);Drop for Schedulernow aborts in-flight leaf I/O, with a regression test.subscribe_progresstransport inpromptforge-model-clientwas deleted after its replacement landed (debt-collected post-run), andworkshop-gateway'sshared-progressedge now declares theserdefeature it was silently inheriting.Verification
cargo clippy(both halves,-D warnings),cargo fmt --all --check, doctests: cleancargo metadata --locked: no outside-product edge to anypromptforge-*crate exceptpromptforge-api