Web server embedding — Implementation Progress
On Sun, 04 Oct 2026, by @lucasdicioccio, 1758 words, 0 code snippets, 0 links, 0images.
Generated from todos/web-server-embedding.progress.md, the repository is the canonical source and may be ahead of this page.
Web server embedding — Implementation Progress
Progress tracker for todos/web-server-embedding.md.
Phase 1 — Storage wiring fix ✅ COMPLETE
Combinators.StoreSessionProgress:SessionSink(SinkBackend/SinkFiles/SinkNone), chosen when the agent is built.agentPersistSessionandsinkStoreCallback.agentStoreSessionandagentStoreSessionWithCallbackkeep their behaviour (backend read from the wrapped agent, else files) on top ofsinkStoreCallback.
CLI/SessionDurable.handleStartderives the conversation ID from the session ID, sosession start --stepno longer writes a secondconv.<random>.jsonnext toconv.<session-id>.json.
Phase 2 — Single agent factory ✅ COMPLETE
- New
System.Agents.AgentFactory:buildAgent :: Tracer IO Trace -> AgentDeps -> AgentRole -> ConversationId -> OSAgentNode -> IO Agent.AgentDeps(API keys, session sink, continuation store, tool cache, completion override),defaultAgentDeps,fileAgentDeps.AgentRole=RootAgent | SubAgent parentConv callStack.Trace(moved fromOneShot, which re-exports it) andmapProgressiveDisclosureTrace.
- Callers moved onto it, and their copies of the agent record, the OpenAI
completion setup, API key lookup, and
toolRegistrationToSystemToolremoved:OneShot.nodeToAgent/nodeToAgentWithThinking(also used by the TUI): thinking output, media injection, and the--session-filecopy are now decorators on the factory’s agent.AgentTree.OneShotTool.turnAgentRuntimeIntoIOTooltakesAgentDepsinstead of aSessionStoreand API keys.OneShotTool.Traceis now a singleOneShotTraceconstructor.MCP.Server.runAgentWithQuery.CLI.SessionDurable.buildAgentForFile(drops its own JSON re-parse).
toolRegistrationToSystemToolhas one definition, exported fromCombinators.ProgressiveDisclosure.Session.Types.newSessionFromPrompt, used bysession start.
Behaviour changes
- Progressive disclosure now applies to the MCP server and the durable
sessioncommands too; before, onlyrun, the TUI, and sub-agents had it. - The MCP server applies the agent’s
executionMode/toolCallPolicyConfigand runs withrunUntilBlocked. A turn waiting on deferred calls returns an error rather than looping forever. - A sub-agent’s session file is named after the conversation ID used for its call-stack entry and OS World entity, not an unrelated random ID.
Verification
cabal build all --enable-testssucceeded. (It did not check warnings: the library was not built with-Wallthen, see the Phase 4 section.)agents-tests: 902 tests pass, including 8 newAgentFactoryTests.- Smoke test of
session start,session start --step, andsession stepwith an isolatedHOME: exactly oneconv.<session-id>.jsonper session.
Phase 3 — Metadata, versions, migrations, catalog ✅ COMPLETE
Session.Types:SessionStatus(idle / ready / running / waiting_external / failed) with text and JSON forms, andsessionStatusOf.isBlockedOnDeferredCalls(fromSession.Loop) andhasBackgroundCalls/backgroundCalls(fromSession.Step) moved here;Session.Loopstill re-exportsisBlockedOnDeferredCalls.SessionStore:SessionLabels,SessionMeta,SessionQuery,VersionConflict.SessionBackendgainssbStoreLabelled,sbLoadMeta,sbCompareAndStore, andsbQuery. Every write increments the version; unconditional stores derive the status, except that a running status is kept.- SQLite:
runMigrationswith a component-scopedschema_migrationstable; migration 2 adds the metadata columns and indexes and derives the status of existing rows. Compare-and-store is a single conditional statement withRETURNING. - File backend:
meta.<uuid>.jsonsidecars. - Composite: writes and queries on the primary, loads with fallback.
SessionCatalog/CatalogEntry,fileCatalog,backendCatalog,isFileBusy.
StoreSessionProgress:sinkStoreCallbackandagentPersistSessiontakeSessionLabels;backendStoreCallbackWith.AgentFactorylabels sessions with the agent slug and, for sub-agents, the parent session.OneShotToolgives a sub-agent’s session the ID of its conversation.- Session tools (
SystemToolbox/Session) read throughintrospectionCatalog;getSessionModTimeremoved.AgentTree.PropstakessessionCatalog; the CLI and TUI passfileCatalog. agents.cabal: the library’simport: warningsmoved abovevisibility. Cabal ignored it in its old position, so the library was built without-Wall -Werrorsince the library was made public.
Not done here
- The search index (
Session/Search) stays on the file store (CLI-only). PRAGMAsettings are left towithHost(Phase 5).
Verification
agents-tests: 923 tests pass, including 21 newSessionMetadataTests.- The “forced full rebuild shows no warnings” claim made here (and in the
Phase 3 commit message) was wrong: that rebuild did not recompile with
-Wall -Werror. See the Phase 4 section for the real check and the fixes.
Warning fixes after re-enabling -Wall -Werror
A rebuild of the library from an empty build directory under -Wall -Werror
reported warnings, now fixed:
- From Phases 2 and 3 (this branch): unused imports in
Tools/SystemToolbox/Session.hs,TUI/Event/Conversation.hs,CLI/SessionDurable.hs, andMCP/Server.hs. - Already present before this branch: a
namebinding shadowing a record field inSession/Step.hs(lateResultsQuery), and a missing signature onresetQuitConfirmationinTUI/Event.hs.
Phase 4 — Continuation consistency ✅ COMPLETE
Session.Async:ContinuationStore.csFindSession: the session of a pending or completed token.- The continuation schema goes through
runMigrations(componentcontinuations); migration 2 adds a(session_id, completed_at)index. csCompletedetects the update withRETURNINGinstead ofSELECT changes().
Session.Wake:wakeSessionWithreturns aWakeOutcome(applied / already completed / unknown tokens) and marks applied tokens completed in the store.wakeSessionandwakeSessionWithCachewrap it.findSessionForToken(index first, then a scan of the backend) andsessionHasToken.
Not done here
- The CLI
completecommand keeps scanning the file store: CLI agents have no continuation store.
Verification
- Library rebuilt from an empty build directory under
-Wall -Werror: clean. cabal build all --enable-testssucceeded.agents-tests: 930 tests pass, including 7 newContinuationConsistencyTests.
Phase 5 — Session runner ✅ COMPLETE
System.Agents.Host:Host,HostConfig/defaultHostConfig,HostTrace,HostError,withHost(WAL, busy timeout, migrations, agent files, sub-agent tools storing into the database).System.Agents.Host.Runner:SessionRunnerwithcreateSession,postMessage,resume,completeCall,cancelRun,getSession,awaitRun,deleteSession(dry run and cascade),subscribe,recoverOnStartup,runnerStats, and an idle-session reaper.Session.Types:DeferredCallViewandpendingDeferredCalls; the CLI’sextractDeferredCallswraps them.Session.Async:csCountSessionandcsDeleteSession.
Differences from the first version of the spec (spec updated)
- Results completed during a run are queued and applied by the run, instead
of being written by
completeCall, which would make the run’s next versioned store conflict. - Events use one runner-wide broadcast channel;
subscribereturns a blocking “next event” action for one session, which survives eviction. NoActiveRunerror;csCountSessionfor dry runs; deletion also refused while an ancestor runs; runner agents always run asynchronously.- After a cancel, background calls below the head turn are reported as
cancelled by the next run (late results), not by
cancelRunitself.
Verification
- Library under
-Wall -Werror: clean.cabal build all --enable-testssucceeded. agents-tests: 940 tests pass, including 10 newRunnerTests. The runner tests passed 15 repeated runs. The test suite is not built with-threaded; the bundled SQLite isTHREADSAFE=1(serialized), which the threaded server in Phase 6 relies on.
Phase 6 — agents-server executable ✅ COMPLETE
examples/agents-server/:src/AgentsServer/Api.hs: the wai application (routing, JSON views,wait/timeout, SSE with keepalives and a shutdown flag).src/AgentsServer/Log.hs: JSON-line logs on stderr, traces summarised field by field.src/AgentsServer/Server.hs: CLI options, startup (host, runner, startup recovery), warp settings, SIGTERM/SIGINT handling.app/Main.hs,test/Main.hs.
agents.cabal: private libraryagents-server-internal, executableagents-server, test suiteagents-server-tests(all threaded where it matters). wai 3.2.4 and warp 3.4.12 were already in the build plan, so nothing new is downloaded.Host.Runner:- Fixed
subscribe: it filtered withretryafterreadTChan, which rolls the read back, so a subscriber blocked forever on the first event of another session. The Phase 5 tests only ever had one session emitting. New regression testa subscriber skips other sessions' events(fails on the old code: no events after 5 s). - New
subscribeSTM, used by the events stream.
- Fixed
documentation/agents-server.md; links fromdocumentation/durable-workflows-howto.mdandREADME.md; added toextra-doc-files.
Differences from the first version of the spec (spec updated)
- The app code is a private sub-library so the test suite can run it.
SessionMetaViewis theSessionMetaJSON, so it includesowner(always null for now).- Status codes:
202exactly when the stored status isrunningat the time of answering,200otherwise; creation always201, with aLocationheader. - In the events stream, a run’s
session.updated(running version) comes just before itsrun.started: the runner stores, then announces. - Additions:
--live-session-ttl,--shutdown-grace,filenameon media,modeon resume, 404not_found, 405, 413 (32 MiB bodies), listlimit1–500 (default 50), comma-separatedstatus. - On shutdown, waiting requests answer at once and event streams end, so the grace period is only for other requests.
Verification
cabal build all --enable-testsunder-Wall -Werror: clean.agents-tests: 941 tests pass (one new runner test).agents-server-tests: 7 tests pass; 30 repeated runs plus 10 with+RTS -N4, no failures.- Smoke tests of the binary against a fake OpenAI endpoint:
- health, agents, and a created session answered by the LLM;
- a deferred call completed over HTTP, with the events stream;
- a restart on the same database, with the session still there;
- SIGTERM with an open events stream: exit 0 in 0.01 s;
- the API key reached the LLM but not the log.
- The guide’s Haskell snippet typechecks.
Phase 7 — Authentication and owners ✅ COMPLETE
- Spec: new “Milestone 2” section planning Phases 7–12, with the defaults taken recorded as decisions 5–10.
SessionStore:SessionQuery.sqOwner(SQLite, file, composite), and migration 3 adding an(owner, updated_at)index.Host.Runner:createSessionAs(with an owner) andsessionOwner, which returns the owner of the root session.agents-server:AgentsServer.Auth: loads the tokens file (sha256or plaintokenentries), compares tokens by SHA-256 digest, parsesBearerheaders.--auth-tokens: 401unauthorizedwithout a valid token (/healthzstays open). Every session endpoint checks the owner, answering 404 for another owner’s session. Continuations check the owner of the token’s session, answering 404unknown_token. Listing filters by owner, or by an ownedparent.server.startedlogsauthentication: bearer|none.
documentation/agents-server.md: an Authentication section.
Not done here
- Per-owner API keys and a default isolation policy (see “Remaining later work” in the spec).
Verification
cabal build all --enable-testsunder-Wall -Werror: clean.agents-tests: 941 pass. The delete test now also checks that a sub-session has its root’s owner. The migrations test expects migrations 1–3.agents-server-tests: 9 pass, including the two-owner flow and loading a tokens file.
Phase 8 — MCP over HTTP ✅ COMPLETE
AgentsServer.Mcp: the Streamable HTTP transport, answering with JSON, oneask_<slug>tool per root agent, runs through the session runner.AgentsServer.Api:- routes
POST /mcp(405 for other methods); - refuses non-loopback
Origins when authentication is off (403forbidden_origin), as the MCP transport requires against DNS rebinding; waitForRunis shared by the REST and MCP handlers.
- routes
documentation/agents-server.md: an MCP over HTTP section, and the origin rule.
Verification
agents-server-tests: 12 pass. New tests: an MCP flow (initialize with version negotiation, the notification’s 202, tools/list, tools/call with_meta.session_id, unknown tool, unknown method, batch, GET 405), a deferred call reported through MCP then completed over REST, and origin checks with and without authentication.- The official Python MCP SDK (2.2.0,
streamable_http_client) against the binary and a fake OpenAI endpoint: initialize (2025-06-18), list tools, callask_weather, which reported the deferred call and its token.
Phase 9 — TUI out of the core library ✅ COMPLETE
git mvof 25 modules fromsrc/totui/:TUI.*exceptToolCallActivity, plusCLI.TUI,CLI.Config, andCLI.agents.cabal: new public libraryagents-tui.agents-libloses those modules andbrick,vty,text-zipper,data-clist.agents-exeaddsagents-tui.documentation/architecture.md: a “Libraries and executables” table.
Verification
cabal build all --enable-tests: clean. The build plan shows brick, vty, and text-zipper only underagents-tui, and nowhere inagents-lib’s transitive closure.cabal test all: 941 + 12 pass.agents-exe --helpruns.
Phase 10 — Postgres backend ✅ COMPLETE
agents-postgreslibrary andagents-postgres-testssuite (see the spec).Host.withHostStores/HostStores;withHostuses it.agents-server --db postgresql://…;redactDatabasefor the log.- Docs: a Postgres section in
documentation/agents-server.md, and the library in the table indocumentation/architecture.md.
Verification
agents-postgres-tests: 5 pass against a throwaway Postgres 16 cluster.- Smoke test of the binary on a Postgres 16 cluster:
- created a session blocked on a deferred call, restarted the server, and
completed the call: the session reached
idle, with consistent rows; - the URL’s password is absent from the log, which holds only JSON lines.
- created a session blocked on a deferred call, restarted the server, and
completed the call: the session reached
Phase 11 — Token streaming ✅ COMPLETE
LLMs/OpenAIStream.hs(new),HttpClient.postStream,OpenAI.callLLMPayloadStreamingandwithOverloadedRetry,OpenAICompletionConfig.cfgOnTextDelta,AgentDeps.adOnTextDelta,HostConfig.hcStreamTokens,SessionEvent.TextDelta,agents-server --stream-tokens.- Docs: a “Streaming answers” section and the
text.deltaevent indocumentation/agents-server.md.
Verification
agents-tests: 5 newOpenAIStreamTests.agents-server-tests: 13 pass, including streaming against a fake streaming endpoint.- The non-streaming path through the binary still works (fake endpoint, deferred call reported).
Phase 12 — Agents from the database ✅ COMPLETE
AgentStore(new),AgentTree.loadAgentTreeFromConfig, stored agents inHost,mkPostgresAgentStore,/v1/agents/:slugendpoints,--admin-owners. See the spec for the differences from the plan.- Docs: a “Storing agents” section in
documentation/agents-server.md.
Verification
agents-tests: 947 pass (a new runner test for stored agents).agents-server-tests: 15 pass.agents-postgres-tests: 6 pass.- Smoke test of the binary with authentication:
- a non-admin PUT is refused (403); an admin PUT stores an agent whose MCP
server is
agents-exe mcp-server, and its tool (mcp_weather_ask_weather_000) is listed; - after a restart the agent is reloaded from SQLite with its tool;
- no tokens in the logs, and no MCP server process left after shutdown.
- a non-admin PUT is refused (403); an admin PUT stores an agent whose MCP
server is