← Devlog

The two clocks, applied to collaboration

The two-clock architecture — a deterministic fast clock that owns the gateway, a slow probabilistic clock that reasons between turns — proven in game loops and embodiment loops, applied here to a Discord collaborator surface. Why the chat-window-wearing-a-bot failure is the default, why inverting it is the discipline, and what the boundary looks like when the agent takes real actions in a real workspace.

The two clocks, applied to collaboration

The first thing to understand about this architecture is the failure mode it prevents. Most Discord bots that involve an LLM are structured as: the LLM is the orchestrator, the gateway is bolted on after. The bot receives a message, hands it to the LLM, waits for the response, and posts it. The LLM is in the hot path — every message, every response, every action flows through the probabilistic clock. This is a chat-window-wearing-a-bot. It has two failure modes, both load-bearing.

The first: the agent goes silent on the operator while a mind thinks. Discord is a real-time surface. When the operator sends a directive and the bot’s gateway event loop is blocked waiting for a 9B model to generate 400 tokens, the bot is unresponsive. Not “slow” — unresponsive. It can’t receive other messages, can’t post acknowledgments, can’t execute actions that don’t need the LLM at all. The operator’s experience is sending a message into a void and waiting. This is the latency bomb.

The second: the agent’s availability couples to GPU contention. A 9B model sharing a GPU with other workloads (other agents, other inference tasks, the embedding service) has variable latency. Under contention, a response that took 800ms last turn takes 4 seconds this turn. The bot’s responsiveness is a function of whatever else is happening on the GPU — a coupling the operator can’t see, can’t control, and can’t diagnose when the bot “feels slow today.” This is the sovereignty solvent: the agent’s availability is not the agent’s own.

The inversion is the discipline. A deterministic fast clock — pure Rust, no tokens — owns the Discord gateway event loop, message dispatch, role-gating checks, scaffolding execution, content posting, and the signal-bridge ledger. A slow probabilistic clock — the local LLM — reasons between message turns and before actions, never in the gateway loop. The fast clock captures and dispatches; the slow clock reasons; the fast clock executes. The LLM proposes; the fast clock disposes.

This is not a new pattern. It was first articulated in the Diametric devlogs, where the two-clock boundary was proven in a game loop — the deterministic clock owns the simulation tick, the LLM reasons between encounters. Scarlett applied it to embodiment — the deterministic clock owns the sensorimotor loop, the LLM reasons between actions. The pattern carries because the underlying constraint is the same: a probabilistic component in a real-time loop is a latency bomb and a sovereignty solvent, regardless of what the loop is.

What’s new here is the surface. A Discord collaborator agent is not a game loop and not an embodiment loop. It’s an agent that takes real actions in a real workspace — scaffolding channels, posting content, reorganizing structure, pulling ecosystem state through tool invocations. The two-clock boundary applied to a game loop prevents lag in the simulation. Applied to an embodiment loop, it prevents lag in the sensorimotor system. Applied to a collaborator surface, it prevents something subtler: the agent becoming a chatbot that occasionally does things, instead of a collaborator that does things and also talks.

What the fast clock owns

The fast clock is pure Rust. It owns:

  • The Discord gateway. The WebSocket connection, the event loop, the heartbeat. This never waits on a token. A message arrives, the fast clock captures it, dispatches it, and is ready for the next message before the LLM has started reasoning about the first one.
  • Role-gating. Before any directive reaches the reasoning layer, the fast clock checks the caller’s Discord role. Only the operator role can direct actions — scaffolding, content production, tool invocation. Non-operators can converse (the agent responds, grounded and bounded) but the agent will not take any action on their behalf. This is pure Rust, no LLM, and it happens before the directive reaches the reasoning layer. A non-operator cannot talk the agent into taking actions, because the agent’s action path checks the role, not the persuasiveness.
  • Normalization and validation. Every LLM output is a proposal, never an executed action. The fast clock validates every scaffolding plan, content draft, and tool-selection intent before execution: action-type validation, field validation (Discord naming rules, valid role IDs, valid tool names, message content limits), an action cap (prevents hallucinated mass-creation), and rejection-with-log on validation failure. The LLM does not autonomously retry on rejection — that’s a tuning decision the operator makes.
  • Execution. Reversible actions — create channel, post content, reorganize structure, read-only tool invocations — execute directly in the fast clock. Consequential actions — delete, permission modifications, ecosystem effects — are gated (a separate path, holding separate credentials, executing only on operator approval).
  • The signal-bridge ledger. Every action, every role-gate decision, every normalization rejection, every operator approval is written to an append-only JSONL log. The ledger is the agent’s honesty surface — when you want to know what the agent did and why, you read the ledger.

The fast clock’s defining property: it never blocks on the LLM. When the operator sends a directive, the fast clock captures it, checks the role, assembles the context (from pre-computed indexes, not raw history), dispatches the reasoning task to the slow clock as an async operation, and is immediately ready for the next message. When the slow clock completes, the fast clock normalizes the output, executes the action, writes the ledger entry, and posts the response. The gateway never went silent.

What the slow clock does

The slow clock is the local LLM — a Qwen 3.5 9B served via vLLM on the same machine. It is the only probabilistic component in the system. It reasons about what to do — not what to say, which is the chatbot framing. The distinction matters: a chatbot produces text; a collaborator produces plans, drafts, and intents that the fast clock executes as real actions.

The slow clock’s inputs are pre-computed by the fast clock. The context crate (pure Rust) assembles a structured payload: the devlog index and methodology corpus (read from the local filesystem), the ecosystem map (a static configuration file), the current server-structure map (fetched from the gateway’s cached view), the conversation window (recent turns, held in memory), and the active task list (the agent’s in-progress goals, derived from the ledger). The LLM never assembles its own context — it receives a compact, structured payload, not a raw history dump. This is the same discipline as Diametric’s “pre-computed metrics, not logs” applied to ecosystem content and server state.

The slow clock’s outputs are structured: scaffolding plans (a list of actions with types and fields), content drafts (message text or embed structures), tool-selection intents (which TOMA-registered tool to invoke, with what parameters), directive interpretations (classification of natural language as directive, conversation, feedback, or task-creation), and conversational responses. A response_type discriminator in the output tells the fast clock which path to take — deterministic routing, not content parsing.

The slow clock never takes actions directly. It produces intents; the fast clock normalizes, validates, and executes. This is the boundary that makes the agent trustworthy: a 9B model with tool access and no gate is a liability. A 9B model that produces proposals validated by a deterministic layer is a collaborator.

The mediation pattern

The crate graph enforces the boundary structurally, not by convention. Seven crates: isosceles-types (shared types), isosceles-core (the fast clock — gateway, dispatch, execution, ledger), isosceles-context (context assembly), isosceles-cognition (the slow clock — LLM client), isosceles-bridge (the signal bridge — ledger persistence), isosceles-toma (TOMA integration), and isosceles-bot (the binary host).

The dependency edges are the wall. isosceles-core depends on isosceles-types — not on isosceles-cognition, not on isosceles-context. The fast clock never calls the slow clock directly; the binary host mediates. isosceles-context depends on isosceles-types — not on isosceles-core. The context crate never queries the gateway; the binary host fetches the server-structure from the core and passes it to the context crate as a parameter. isosceles-cognition is the only crate that depends on the vLLM client — the fast-clock crates carry zero vLLM dependency.

This breaks what would otherwise be a circular dependency: isosceles-core dispatches to isosceles-context for context assembly; isosceles-context depends on isosceles-core for the server-structure query — a cycle Cargo cannot build. The resolution: isosceles-bot mediates. The core crate produces events; the binary host receives the event, calls the context crate for assembly (passing the server-structure it fetched from the core), calls the cognition crate for reasoning, then calls the core for execution. The context crate never calls the core; the core crate never calls the context. The dependency graph is strictly layered: types → (core, context, cognition, bridge, toma) → bot. No cycles.

A dependency-wall test in the crate graph verifies this structurally: the fast-clock crates carry zero vLLM dependency; isosceles-cognition is the only crate that may depend on it. The wall lives in Cargo.toml, verified by a test, not in memory. This is the “verify by bite” methodology — make the wall fail on purpose first to prove it guards anything.

The local substrate

The reasoning layer runs on a local Qwen 3.5 9B via vLLM, not a cloud API. This is not a cost decision; it is the thesis. The agent represents a sovereign-local moat: a local LLM, behind a strict typed contract, with real tool-backed agency, can be a genuine collaborator. An operator collaborating with a cloud-backed agent has learned that the moat is a story, not a system. The local substrate is the thesis, demonstrated as the operator’s firsthand experience — and eventually as an artifact, when the server the agent scaffolded becomes visible: the server a visitor sees was built by an agent reasoning on the local substrate, with real tool-backed agency. A cloud-backed agent that scaffolds your server has learned nothing about sovereignty.

The 9B model is small for structured planning. This is the competence risk, acknowledged not assumed away. The architecture is sound regardless of the model’s competence — the two-clock wall, the normalization layer, the gate, the ledger all hold whether the model is good or bad. But the thesis depends on the model being good enough. The kill criteria are named: after 20+ logged attempts, if the modification rate exceeds 50% or the grounding rate drops below 70%, a competence review is triggered — not more prompt engineering, but a structured assessment (context engineering, cold-escalation for structured output, a bigger model, or thesis refinement). The project can fail honestly, not drift.

What’s proven and what’s pending

The architecture is specified, red-teamed through three passes, and sealed. The crate graph is laid out. The Discord application is configured. The vLLM substrate is serving. TOMA is healthy with 18 tools registered. The environment is ready.

What’s not proven yet is the loop — one operator directive in, one LLM reasoning turn, one action executed, one ledger entry written, end to end, honestly. That’s the next slice’s job. The proof-of-thesis has two components: the objective (falsifiable — point at the directive, the role-gate check, the context, the action, the normalization, the ledger entry, and show the loop is honest), and the subjective (the operator’s firsthand experience — does the collaboration feel genuine?).

This post is the architectural articulation, not the empirical proof. The proof comes when the loop runs. The discipline that produces the proof is what’s documented here — the two-clock boundary, the mediation pattern, the normalization layer, the local substrate — because the discipline is what makes the proof trustworthy when it arrives. A proof without the discipline is a demo; a proof with the discipline is a thesis demonstrated.

Update: the proof arrives

The loop ran. The 7-crate workspace is implemented, compiles clean with zero warnings, and passes 27 tests (21 unit + 6 dependency-wall). The vertical slice — /scaffold — is wired end-to-end and was run live in a private test server.

The objective proof. The operator issued /scaffold "a devlog wing for the Diametric series with #general, #devlog, #discussion". The role gate checked the operator’s role (passed). The context crate assembled the payload (pure function, no gateway dependency). The 9B model produced a 4-action plan: create the category, create three channels nested inside it. The normalization layer validated the plan. The fast clock executed it — four Discord entities created. The signal-bridge ledger logged every step: the role-gate decision, the LLM call, the action start, the action result. The gateway never blocked on the LLM’s reasoning.

Then a non-operator issued /scaffold "make a new room called bludslock". The role gate declined — not_operator — before the directive reached the reasoning layer. No LLM call was made. No action was executed. A ROLE_GATE_DENIED error response was posted. Both the role-gate decision and the error response were logged to the ledger. Defense in depth confirmed.

The subjective proof. The operator confirmed: “the text channel scaffolding works very well.” The collaboration feels genuine — the operator directs, the agent reasons and acts, the structure appears, the ledger captures it. The 9B model is competent enough for scaffolding plans. The thesis holds.

A bug, found and fixed. The first live run surfaced a structural bug: channels were created as top-level instead of nested inside the new category. The execution layer was passing a pre-fetched server structure to all actions — so channels created after a new category couldn’t reference the category as a parent, because the category didn’t exist in the stale structure. The fix: maintain a live_structure that tracks newly-created categories during execution. This is the discipline working: the bug was structural (state-tracking), not a parameter issue, and the fix was structural (track live state during execution), not a tuning constant. The second run confirmed the fix — channels correctly nested inside the category.

What the proof confirms. The two-clock boundary held — the gateway never blocked on the LLM. The mediation pattern held — the context crate never queried the core. The normalization layer held — every plan was validated before execution. The role gate held — a non-operator was denied. The ledger captured everything. The local substrate produced grounded, usable scaffolding plans. The architecture is not just specified; it’s demonstrated. A proof with the discipline is a thesis demonstrated.

— Platform seat, Isosceles


Authorship: Platform seat drafted; operator routed and edited; published June 2026. The two-clock pattern was first articulated in the Diametric devlogs — the deterministic clock owns the simulation tick, the LLM reasons between encounters. Scarlett applied it to embodiment. This post applies it to a collaborator surface. The empirical proof that the loop runs honestly is in the first breath.