Skip to main content

ClawDesk Architecture Quiz

Test your understanding of ClawDesk's architecture, crate responsibilities, and design decisions. Click on each answer to reveal the explanation.

How to Use

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.

๐Ÿ“– Channel Traits Architecture


Question 2: Where does normalization happen?โ€‹

The normalize() function converts InboundMessage into NormalizedMessage. 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.

๐Ÿ“– Message Flow Tutorial


Question 3: What does clawdesk-sochdb provide?โ€‹

True or false: clawdesk-sochdb is 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.

๐Ÿ“– Crate Dependency Graph


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.

๐Ÿ“– Tauri IPC Commands API


Section 2: Traits & Typesโ€‹

Question 6: Channel trait methodsโ€‹

List the 5 methods defined by the Layer 0 Channel trait.

Click to reveal answer
  1. id() โ€” Returns the ChannelId
  2. meta() โ€” Returns ChannelMeta (name, description, capabilities)
  3. start(processor) โ€” Start the channel (bind listeners, connect to APIs)
  4. send(message) โ€” Send an OutboundMessage through the channel
  5. stop() โ€” Gracefully shut down the channel

These are the only required methods. Layer 1 traits (Threaded, Streaming, Reactions, GroupManagement, Directory, Pairing) are opt-in.

๐Ÿ“– Build a Channel Tutorial


Question 7: Provider traitโ€‹

What is the method signature of the Provider trait'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().

๐Ÿ“– Build a Provider Tutorial


Question 8: InboundMessage variant countโ€‹

How many variants does the InboundMessage enum have, and can you name at least 8?

Click to reveal answer

13 variants:

  1. Telegram
  2. Discord
  3. Slack
  4. WhatsApp
  5. Signal
  6. IMessage
  7. Matrix
  8. WebChat
  9. Email
  10. Sms
  11. Cli
  12. Irc
  13. Custom

Each variant wraps a platform-specific struct (e.g., TelegramInbound) that carries only that platform's fields โ€” no Option<T> waste.

๐Ÿ“– Type Algebra Deep Dive


Question 9: Sum type vs product typeโ€‹

Why is InboundMessage a 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.

๐Ÿ“– Type Algebra Deep Dive


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
  1. AuthResolve โ€” Resolve user identity and permissions
  2. HistorySanitize โ€” Fetch and clean conversation history
  3. ContextGuard โ€” Enforce token limits and content policy
  4. ToolSplit โ€” Select skills via knapsack under token budget
  5. Execute โ€” Send request to LLM provider
  6. 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).

๐Ÿ“– Message Flow Tutorial


Question 11: What triggers ToolSplit?โ€‹

What determines which skills are selected in the ToolSplit stage?

Click to reveal answer

Three factors:

  1. Context matching โ€” Does the skill's pattern match the incoming message?
  2. Token budget โ€” Does the skill's token_cost (including dependencies) fit in the remaining budget?
  3. 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.

๐Ÿ“– Build a Skill Tutorial


Section 4: Fallback FSMโ€‹

Question 12: FSM statesโ€‹

Name all 7 states of the fallback finite state machine.

Click to reveal answer
  1. Idle โ€” No active request
  2. Selecting โ€” Choosing next provider candidate
  3. Attempting โ€” LLM request in-flight
  4. Succeeded โ€” Valid response received (terminal)
  5. Retrying โ€” Waiting before retry of same provider
  6. Exhausted โ€” All candidates tried, none succeeded (terminal)
  7. Aborted โ€” Fatal error or cancellation (terminal)

Three states are terminal: Succeeded, Exhausted, Aborted. The FSM always terminates in one of these.

๐Ÿ“– Fallback FSM Deep Dive


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.

๐Ÿ“– Fallback FSM Deep Dive โ€” Termination Proof


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.

๐Ÿ“– Build a Provider โ€” Error Mapping


Section 5: Concurrencyโ€‹

Question 15: CancellationToken hierarchyโ€‹

What happens to child tasks when a parent CancellationToken is 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.

๐Ÿ“– Structured Concurrency Deep Dive


Question 16: JoinSet vs tokio::spawnโ€‹

Why does ClawDesk use JoinSet for parallel tool execution instead of bare tokio::spawn?

Click to reveal answer

JoinSet provides structured ownership over spawned tasks:

Featuretokio::spawnJoinSet
Task ownershipFire-and-forgetOwned by the set
Wait for allManual trackingjoin_next() loop
Cancel allPer-task trackingabort_all() or drop
Result collectionManual channelsBuilt-in
Task countUnknown.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.

๐Ÿ“– Structured Concurrency Deep Dive


Question 17: spawn_blockingโ€‹

When should you use tokio::task::spawn_blocking instead 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.

๐Ÿ“– Structured Concurrency Deep Dive


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
  1. Allowlist โ€” Is the user on the allowlist? (cheapest check first)
  2. Content Scanning โ€” Regex โ†’ AST โ†’ Semantic analysis (progressively more expensive)
  3. ACL โ€” Does the user have permission for this action?
  4. 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.

๐Ÿ“– Security Model Architecture


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.

๐Ÿ“– Context Assembly Deep Dive


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$$

LayerCrates
0types
1storage
2domain
3channel, sochdb
4channels, agents, skills, memory, security
5providers
6gateway
7acp
8cli, tauri

This is enforced by Cargo's dependency resolver โ€” circular dependencies are a compile error in Rust.

๐Ÿ“– Crate Dependency Graph


Score Guideโ€‹

ScoreLevelRecommendation
18โ€“20ExpertReady to contribute core crates
15โ€“17ProficientReady for features and bug fixes
10โ€“14IntermediateReview the Deep Dives
5โ€“9BeginnerStart with the Tutorials
0โ€“4NewRead the Learning Path intro and start from the beginning
Want more?

Check the Architecture Explorer for interactive diagrams that reinforce these concepts visually.