ClawDesk Architecture Quiz
Test your understanding of ClawDesk's architecture, crate responsibilities, and design decisions. Click on each answer to reveal the explanation.
Work through each question, form your answer, then click "Click to reveal answer" to check. Aim for 15/20 to confirm solid understanding.
Section 1: Crate Responsibilitiesโ
Question 1: Which crate defines InboundMessage?โ
A message arrives from Telegram. Which crate contains the enum definition for
InboundMessage?
Click to reveal answer
clawdesk-channel
clawdesk-channel defines the Channel trait and all associated types including InboundMessage, OutboundMessage, ChannelId, and ChannelMeta. Don't confuse it with clawdesk-channels (plural), which contains the implementations for specific platforms like Telegram and Discord.
Question 2: Where does normalization happen?โ
The
normalize()function convertsInboundMessageintoNormalizedMessage. Which crate owns this function?
Click to reveal answer
clawdesk-domain
Normalization is a domain concern โ it maps platform-specific representations into the application's canonical form. The domain crate sits between channel (Layer 3) and the agent pipeline (Layer 4) in the dependency DAG.
Question 3: What does clawdesk-sochdb provide?โ
True or false:
clawdesk-sochdbis responsible for all of ClawDesk's persistent storage.
Click to reveal answer
False โ but it's close.
clawdesk-sochdb is the SochDB integration crate โ it provides the ACID-compliant embedded vector database used for conversation history, knowledge retrieval, and context assembly. However, clawdesk-storage defines the abstract storage traits, and clawdesk-memory provides the higher-level memory service that uses SochDB.
The dependency chain: clawdesk-memory โ clawdesk-sochdb โ clawdesk-storage โ clawdesk-types
๐ Storage Layer Architecture ยท clawdesk-sochdb Reference
Question 4: Event bus crateโ
Which crate would you modify to add a new system-wide event (e.g., a "provider health changed" event)?
Click to reveal answer
clawdesk-domain
Domain events are defined in clawdesk-domain because they represent business-level occurrences that multiple crates may need to react to. The domain crate is the natural boundary โ it's high enough to be meaningful but low enough in the dependency graph that most crates can depend on it.
Question 5: Where is the Tauri IPC bridge?โ
You want to add a new IPC command that the desktop frontend can call. Which crate do you modify?
Click to reveal answer
clawdesk-tauri
The Tauri crate sits at Layer 8 (the top of the DAG) alongside clawdesk-cli. It defines IPC commands using Tauri's #[tauri::command] macro, which the frontend can invoke. These commands delegate to the ACP (Agent Communication Protocol) layer below.
Section 2: Traits & Typesโ
Question 6: Channel trait methodsโ
List the 5 methods defined by the Layer 0
Channeltrait.
Click to reveal answer
id()โ Returns theChannelIdmeta()โ ReturnsChannelMeta(name, description, capabilities)start(processor)โ Start the channel (bind listeners, connect to APIs)send(message)โ Send anOutboundMessagethrough the channelstop()โ Gracefully shut down the channel
These are the only required methods. Layer 1 traits (Threaded, Streaming, Reactions, GroupManagement, Directory, Pairing) are opt-in.
Question 7: Provider traitโ
What is the method signature of the
Providertrait's primary method?
Click to reveal answer
async fn send(&self, req: ProviderRequest) -> Result<ProviderResponse, ProviderError>
The trait is intentionally minimal โ one method for sending requests. The complexity lives in error mapping for the fallback FSM. The trait also defines id(), supported_models(), and health().
Question 8: InboundMessage variant countโ
How many variants does the
InboundMessageenum have, and can you name at least 8?
Click to reveal answer
13 variants:
TelegramDiscordSlackWhatsAppSignalIMessageMatrixWebChatEmailSmsCliIrcCustom
Each variant wraps a platform-specific struct (e.g., TelegramInbound) that carries only that platform's fields โ no Option<T> waste.
Question 9: Sum type vs product typeโ
Why is
InboundMessagea sum type (enum) rather than a product type (struct with Options)? Give the information-theoretic argument.
Click to reveal answer
A struct with 60 Option<T> fields creates $2^{60} \approx 10^{18}$ possible states, but only 13 are valid. That's 56.3 bits of wasted entropy โ states the type allows but that never occur.
The enum has exactly 13 variants, requiring only $\lceil\log_2(13)\rceil = 4$ bits. Every representable state is a valid state.
$$\frac{|\text{Valid}|}{|\text{Total}_\text{struct}|} = \frac{13}{2^{60}} \approx 10^{-17}$$
Additionally, the enum gives exhaustive matching โ the compiler ensures every variant is handled. With the struct approach, you must manually check which fields are populated, with no compiler help.
Section 3: Agent Pipelineโ
Question 10: Pipeline stage orderingโ
Put these 6 pipeline stages in the correct order:
ContextGuard,FailoverDecide,HistorySanitize,AuthResolve,Execute,ToolSplit
Click to reveal answer
- AuthResolve โ Resolve user identity and permissions
- HistorySanitize โ Fetch and clean conversation history
- ContextGuard โ Enforce token limits and content policy
- ToolSplit โ Select skills via knapsack under token budget
- Execute โ Send request to LLM provider
- FailoverDecide โ Evaluate response, trigger fallback if needed
The order is deliberate: authentication must come first (reject unauthorized early), history before context guard (need history to compute token budget), tool selection before execution (tools are part of the request), and failover after execution (needs the result to decide).
Question 11: What triggers ToolSplit?โ
What determines which skills are selected in the ToolSplit stage?
Click to reveal answer
Three factors:
- Context matching โ Does the skill's pattern match the incoming message?
- Token budget โ Does the skill's
token_cost(including dependencies) fit in the remaining budget? - Priority weight โ Among fitting skills, higher priority/cost ratio wins
The selector uses a greedy knapsack approximation after topological sort of dependencies:
$$\max \sum_{i} w_i \cdot x_i \quad \text{s.t.} \quad \sum_{i} c_i \cdot x_i \leq B$$
Solved in $O(k \log k)$ where $k$ is the number of candidate skills.
Section 4: Fallback FSMโ
Question 12: FSM statesโ
Name all 7 states of the fallback finite state machine.
Click to reveal answer
- Idle โ No active request
- Selecting โ Choosing next provider candidate
- Attempting โ LLM request in-flight
- Succeeded โ Valid response received (terminal)
- Retrying โ Waiting before retry of same provider
- Exhausted โ All candidates tried, none succeeded (terminal)
- Aborted โ Fatal error or cancellation (terminal)
Three states are terminal: Succeeded, Exhausted, Aborted. The FSM always terminates in one of these.
Question 13: Termination boundโ
What is the maximum number of transitions the fallback FSM can make with $n$ provider candidates and 1 retry per provider?
Click to reveal answer
$$T_{\max} = 2n + 1$$
More generally, with $r$ retries per provider: $T_{\max} = n \cdot (r + 1) + 1$
The proof relies on the progress function: the candidate queue is finite and strictly decreasing. Each provider is attempted at most $r + 1$ times before being removed.
Question 14: Error classificationโ
A provider returns HTTP 429 (Too Many Requests). How does the FSM classify this error, and what transition occurs?
Click to reveal answer
- Error:
ProviderError::RateLimit(retry_after) - Classification:
ErrorClass::Transient - FSM Event:
TransientFailure - Transition:
Attempting โ Retrying
The FSM waits for the retry_after duration, then transitions Retrying โ Attempting to retry the same provider. If the retry also fails, it transitions Retrying โ Selecting to try the next candidate.
Compare with HTTP 401 (Auth Error), which is Fatal and causes Attempting โ Aborted immediately.
Section 5: Concurrencyโ
Question 15: CancellationToken hierarchyโ
What happens to child tasks when a parent
CancellationTokenis cancelled?
Click to reveal answer
All child tokens are immediately cancelled. Cancellation propagates automatically from parent to child via child_token():
let parent = CancellationToken::new();
let child = parent.child_token();
let grandchild = child.child_token();
parent.cancel();
// child.is_cancelled() == true
// grandchild.is_cancelled() == true
This is zero-cost propagation โ no event listeners, no polling, no manual wiring. Compare to Node.js AbortSignal which requires explicit addEventListener('abort', ...) at each level.
Question 16: JoinSet vs tokio::spawnโ
Why does ClawDesk use
JoinSetfor parallel tool execution instead of baretokio::spawn?
Click to reveal answer
JoinSet provides structured ownership over spawned tasks:
| Feature | tokio::spawn | JoinSet |
|---|---|---|
| Task ownership | Fire-and-forget | Owned by the set |
| Wait for all | Manual tracking | join_next() loop |
| Cancel all | Per-task tracking | abort_all() or drop |
| Result collection | Manual channels | Built-in |
| Task count | Unknown | .len() |
With tokio::spawn, tasks are detached โ if you forget to track the JoinHandle, the task runs forever (resource leak). With JoinSet, dropping the set aborts all tasks.
Question 17: spawn_blockingโ
When should you use
tokio::task::spawn_blockinginstead of regular async execution?
Click to reveal answer
Use spawn_blocking for CPU-bound work that would block a Tokio worker thread for more than ~1ms:
- Regex matching on large text (security scanning)
- Token estimation on large documents
- Cryptographic operations (HMAC verification)
- JSON serialization of large payloads (>1MB)
- File I/O (synchronous syscalls)
Never use it for network I/O (use async reqwest/hyper instead) or database queries (use async drivers).
spawn_blocking moves the work to a separate blocking threadpool so that Tokio's async worker threads remain free to handle concurrent I/O.
Section 6: Architecture Conceptsโ
Question 18: Security cascade orderโ
What are the 4 layers of the security cascade, and in what order do they execute?
Click to reveal answer
- Allowlist โ Is the user on the allowlist? (cheapest check first)
- Content Scanning โ Regex โ AST โ Semantic analysis (progressively more expensive)
- ACL โ Does the user have permission for this action?
- Audit โ Log the access to the audit trail
The cascade is deliberately ordered by cost: ~95% of messages are cleared or blocked by the allowlist check alone. Only messages that pass all 4 layers are processed.
Question 19: TOON formatโ
What does TOON stand for, and what token savings does it achieve?
Click to reveal answer
TOON = Token-Optimized Output Notation
It achieves 58โ67% fewer tokens compared to standard JSON formatting for context sections.
Example:
// JSON: ~85 tokens
{"conversation_history":[{"role":"user","content":"Hello",...}]}
// TOON: ~32 tokens
[H]
U|10:30|Hello
TOON uses single-character role prefixes, pipe delimiters, section headers, and omits redundant structure. The LLM can still parse it because the format is documented in the system prompt.
Question 20: Crate DAG layeringโ
In the crate dependency graph, what is the fundamental rule that prevents circular dependencies?
Click to reveal answer
Strict layering: A crate at layer $L_a$ may only depend on crates at layers $L_b < L_a$.
$$\text{Layer}(A) > \text{Layer}(B) \Rightarrow A \text{ may depend on } B$$ $$\text{Layer}(A) \leq \text{Layer}(B) \Rightarrow A \text{ must NOT depend on } B$$
| Layer | Crates |
|---|---|
| 0 | types |
| 1 | storage |
| 2 | domain |
| 3 | channel, sochdb |
| 4 | channels, agents, skills, memory, security |
| 5 | providers |
| 6 | gateway |
| 7 | acp |
| 8 | cli, tauri |
This is enforced by Cargo's dependency resolver โ circular dependencies are a compile error in Rust.
Score Guideโ
| Score | Level | Recommendation |
|---|---|---|
| 18โ20 | Expert | Ready to contribute core crates |
| 15โ17 | Proficient | Ready for features and bug fixes |
| 10โ14 | Intermediate | Review the Deep Dives |
| 5โ9 | Beginner | Start with the Tutorials |
| 0โ4 | New | Read the Learning Path intro and start from the beginning |
Check the Architecture Explorer for interactive diagrams that reinforce these concepts visually.