πŸ•·οΈ

proompt

Context Is The Program

The context engine. Where the ideas are.

A block fetches live state and renders it. A wall is a program that spawns blocks and assembles them every turn. A strategy is an ordering with cache breakpoints, because what's stable goes first and the provider bills you for the difference. The conversation is one block among a dozen. It isn't the state.

one per user scope

A wall

class ManagerWall(Wall):
    async def start(self):
        self.system   = await self.spawn(SystemPrompt)
        self.runtime  = await self.spawn(RuntimeContext)     # who is this, what tier
        self.workspace= await self.spawn(UserWorkspace)      # their files, live
        self.knowledge= await self.spawn(WorkingKnowledge)   # what the gate kept
        self.recent   = await self.spawn(RecentConversation) # one block, not the state
        self.now      = await self.spawn(CurrentTime)

        self.strategy("gate",
            GatePrompt, pr.CacheControl(ttl="1h"),
            self.runtime, self.workspace, pr.CacheControl(ttl="5m"),
            self.recent, self.knowledge, self.now)
        self.strategy("main",
            self.system, pr.CacheWarm(name="main", ttl="1h"),
            self.runtime, self.workspace, pr.CacheControl(ttl="1h"),
            self.knowledge, pr.CacheControl(ttl="5m"),
            self.recent, self.now)

        self.user_input(self._ear)

    async def _ear(self, event):
        await self.runtime.refresh()                          # fail closed
        gate = await self._loop(model=CHEAP, toolset=gate_tools, stream=True,
                                scope=CURRENT_SCOPE.get(), context=self.outer)
        main = await self._loop(model=BIG, toolset=main_tools,
                                allowed_tools={"sources": self.runtime.allowed},
                                stream=True, scope=CURRENT_SCOPE.get(), context=self.outer)

Two strategies over the same blocks. The gate runs on a cheap model with a narrow toolset and decides what persists. The main turn runs on the big model with the user's real tools. Both are _loop calls the Hub executes, scoped to the user, rendered from self.outer.

The wall is the ear. User input lands here, not in a UI, not in a router.

fetch, render, tools

A block

class WorkingKnowledge(Block):
    """Whole documents the gate decided to keep. Topic-embedded, not chunked."""

    async def fetch(self):
        self.docs = await kb.kept_for(CURRENT_SCOPE.get())

    def outer(self):
        if not self.docs: return None
        return SystemContent("\n\n".join(f"<doc id={d.id}>{d.text}</doc>" for d in self.docs))

    @tewl(source="pro_tools", category="knowledge")
    async def keep_knowledge_doc(self, args: KeepArgs) -> dict:
        """Persist a matched doc into working knowledge for the main turn."""
        return await kb.keep(CURRENT_SCOPE.get(), args.doc_ids)

fetch() pulls state. outer() renders it, or nothing. Tools declared on a block belong to that block's source, and the wall decides which sources a given turn can see.

Knowledge here is topic-embedded, not chunk-embedded: each doc carries generated topics, user input matches on those, whole documents come back, and the gate keeps or drops them. Retrieval finds candidates. The gate is the editor.

why bother

What it gets you

  1. Cache-aware by construction.Breakpoints are part of the strategy, not an optimization pass. Stable blocks front, volatile blocks back.
  2. Fewer rounds.The gate surfaces what the main turn needs before it runs. A second round is a bug report against the wall.
  3. Dislocated control.A block can run its own loop, route input, and hand control back. The wall is still the arbiter.
  4. Rollout capture.The wall listens to tool calls and results as they stream, and cleans up in a try/finally when the loop ends or is interrupted.
  5. One tenant per wall.Reactive scope mode gives every user their own instance. State cannot cross.