post-cortex-daemon 0.3.1

HTTP / gRPC / SSE / stdio daemon for post-cortex. Hosts the rmcp Model Context Protocol surface, the tonic gRPC API, and ships the `pcx` CLI binary.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
// Copyright (c) 2025, 2026 Julius ML
// MIT License

//! Request payload structs for the 9 consolidated MCP tools.
//!
//! All schemars-decorated fields are kept verbatim so the JSON Schema that
//! `#[tool(input_schema = ...)]` emits matches the previous monolith — clients
//! see the same hints.

use schemars::JsonSchema;
use serde::Deserialize;

// =============================================================================
// Tool 7: assemble_context
// =============================================================================

/// Request payload for the `assemble_context` tool.
#[derive(Deserialize, JsonSchema, Debug)]
pub struct AssembleContextRequest {
    /// Optional session UUID; ignored when `workspace_id` is set.
    #[schemars(
        description = r#"Session UUID. Required when workspace_id is not provided.

Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

Note: If both session_id and workspace_id are provided, workspace_id takes precedence
(context is merged across all sessions in the workspace)."#
    )]
    pub session_id: Option<String>,
    /// Optional workspace UUID; takes precedence over `session_id`.
    #[schemars(
        description = r#"Workspace UUID. When provided, context is merged across all sessions in the workspace.

Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

Note: Takes precedence over session_id."#
    )]
    pub workspace_id: Option<String>,
    /// Query topic used to seed entity extraction and graph traversal.
    #[schemars(description = r#"The query/topic to assemble context for.

Example: "How does authentication interact with the user profile service?"

Note: Entity extraction runs on this query to seed graph traversal."#)]
    pub query: String,
    /// Maximum token budget for assembled context output.
    #[schemars(
        description = r#"Token budget for the assembled context (default: 4000).

Examples:
- 2000: tight context window
- 4000: default (matches Axon's per-call budget)
- 8000: extended chains / orchestration

Note: Must be > 0. Items are packed highest-score-first until budget is consumed."#
    )]
    pub token_budget: Option<u32>,
}

// =============================================================================
// Tool 8: manage_entity
// =============================================================================

/// Request payload for the `manage_entity` tool.
#[derive(Deserialize, JsonSchema, Debug)]
pub struct ManageEntityRequest {
    /// Entity management action (`delete` or `delete_update`).
    #[schemars(description = r#"Action to perform on session content.

Valid values:
- delete: Remove an entity from the session (cascades typed edges). Requires entity_name.
- delete_update: Remove a single context update (ghost / bad row) by entry_id. Requires entry_id.

Note: Must be lowercase. Additional actions may be added later."#)]
    pub action: String,
    /// Session UUID that contains the target entity or update.
    #[schemars(description = r#"Session UUID containing the entity/update.

Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"#)]
    pub session_id: String,
    /// Entity name for the `delete` action.
    #[schemars(
        description = r#"Entity name to operate on (required for action=delete).

Example: "RocksDB"

Note: Names are matched case-insensitively in the entity graph."#
    )]
    pub entity_name: Option<String>,
    /// Context-update ID for the `delete_update` action.
    #[schemars(
        description = r#"Context-update id (required for action=delete_update).

Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

Note: This is the `entry_id` returned by `assemble_context` items, or the id of a
ContextUpdate stored in the session. Use it to remove ghost / mis-shaped writes."#
    )]
    pub entry_id: Option<String>,
}

// =============================================================================
// Tool 9: admin
// =============================================================================

/// Request payload for the `admin` tool.
#[derive(Deserialize, JsonSchema, Debug)]
pub struct AdminRequest {
    /// Administrative action (`health`, `vectorize_session`, `vectorize_stats`, `create_checkpoint`).
    #[schemars(description = r#"Administrative action on the Post-Cortex daemon.

Valid values:
- health: Report daemon health (active sessions, embeddings status, version)
- vectorize_session: Backfill embeddings for an existing session (requires session_id)
- vectorize_stats: Return embedding-pipeline statistics as JSON
- create_checkpoint: Persist a snapshot of a session for later recall (requires session_id)

Examples:
✅ {"action": "health"}
✅ {"action": "vectorize_session", "session_id": "60c598e2-..."}
✅ {"action": "vectorize_stats"}
✅ {"action": "create_checkpoint", "session_id": "60c598e2-..."}

Note: Must be lowercase."#)]
    pub action: String,
    /// Session UUID for actions that target a specific session.
    #[schemars(
        description = r#"Session UUID for vectorize_session and create_checkpoint.

Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"#
    )]
    pub session_id: Option<String>,
}

// =============================================================================
// Tool 1: session
// =============================================================================

/// Request payload for the `session` tool.
#[derive(Deserialize, JsonSchema, Debug)]
pub struct SessionRequest {
    /// Session management action (`create`, `list`, `load`, `search`, `update_metadata`, `delete`).
    #[schemars(description = r#"Action to perform on sessions.

Valid values:
- create: Create a new session and return a UUID (optional: name, description)
- list: List all existing sessions
- load: Load metadata for a single session (requires session_id)
- search: Find sessions by name or description substring (requires query)
- update_metadata: Update session name and/or description (requires session_id; provide name and/or description)
- delete: Permanently delete a session and its updates (requires session_id)

Examples:
✅ {"action": "create", "name": "Auth", "description": "OAuth2 work"}
✅ {"action": "load", "session_id": "60c598e2-..."}
✅ {"action": "search", "query": "auth"}
✅ {"action": "update_metadata", "session_id": "60c598e2-...", "name": "New name"}
✅ {"action": "delete", "session_id": "60c598e2-..."}

Note: Must be lowercase."#)]
    pub action: String,
    /// Session name (used by `create` and `update_metadata`).
    #[schemars(description = r#"Session name (used by create and update_metadata).

Examples:
- "Feature: Authentication"
- "Bug Fix: Memory Leak"

Note: For update_metadata, only provided fields are changed."#)]
    pub name: Option<String>,
    /// Session description (used by `create` and `update_metadata`).
    #[schemars(
        description = r#"Session description (used by create and update_metadata).

Example: "Working on implementing OAuth2 login flow"

Note: For update_metadata, only provided fields are changed."#
    )]
    pub description: Option<String>,
    /// Session UUID (required for `load`, `update_metadata`, `delete`).
    #[schemars(
        description = r#"Session UUID (required for load, update_metadata, delete).

Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

Example: "60c598e2-d602-4e07-a328-c458006d48c7""#
    )]
    pub session_id: Option<String>,
    /// Search query for the `search` action.
    #[schemars(description = r#"Search query for the search action.

Examples:
- "authentication"
- "image pipeline"

Note: Matches session name or description (case-insensitive substring)."#)]
    pub query: Option<String>,
}

// =============================================================================
// Tool 2: update_conversation_context (single + bulk)
// =============================================================================

/// Single context-update item used in bulk mode.
#[derive(Deserialize, JsonSchema, Debug, Clone)]
pub struct ContextUpdateItem {
    /// Interaction type classifier (e.g., `qa`, `decision_made`).
    #[schemars(description = r#"Type of interaction (must be exact lowercase value).

Valid values:
- qa: Questions and answers about the codebase
- decision_made: Architectural decisions and trade-offs
- problem_solved: Bug fixes and solutions to technical problems
- code_change: Code modifications and refactoring
- requirement_added: New requirements or constraints
- concept_defined: Technical concepts and patterns explained

Examples:
✅ "decision_made" (correct)
❌ "DecisionMade" (wrong - must be lowercase)
❌ "made_decision" (wrong - use exact term)"#)]
    pub interaction_type: String,
    /// Key-value content payload for the update.
    #[schemars(
        description = r#"Content as key-value pairs (all values must be strings).

Format: HashMap<String, String> where both keys and values are strings.

Examples:
Simple: {"decision": "Use Rust", "rationale": "Performance"}
Complex: {"criteria": "{\"performance\": 9, \"safety\": 10}", "date": "2025-01-12"}

Note: For complex nested data, stringify as JSON first. Do not pass nested objects directly."#
    )]
    pub content: std::collections::HashMap<String, String>,
    /// Named entities mentioned in this update. Required by the canonical
    /// write path so the entity graph is never silently empty.
    #[serde(default)]
    #[schemars(description = r#"Named entities mentioned in this update.

Each entry: {"name": "<unique name>", "entity_type": "concept|technology|problem|solution|decision|code_component"}.
Required (must contain at least one entity) so the entity graph captures every write."#)]
    pub entities: Vec<post_cortex_mcp::EntityItem>,
    /// Relations between the entities listed above.
    #[serde(default)]
    #[schemars(description = r#"Relations between the entities listed above.

Each entry: {"from_entity": "<name>", "to_entity": "<name>", "relation_type": "depends_on|implements|caused_by|leads_to|related_to|required_by|conflicts_with|solves", "context": "<short why>"}.
Required (must contain at least one relation). Both endpoints must appear in the `entities` array; self-relations and dangling references are rejected with InvalidArgument."#)]
    pub relations: Vec<post_cortex_mcp::RelationItem>,
    /// Optional code location reference.
    #[schemars(description = r#"Optional code reference for context.

Can be a simple string or complex object:

Examples:
- Simple: "src/main.rs:42"
- Complex: {"file": "src/main.rs", "line": 42, "function": "process_data"}

Note: Helps link context to specific code locations."#)]
    pub code_reference: Option<serde_json::Value>,
}

/// Request payload for the `update_conversation_context` tool.
#[derive(Deserialize, JsonSchema, Debug)]
pub struct UpdateConversationContextRequest {
    /// Session UUID to add context to.
    #[schemars(description = r#"Session ID (36-char UUID format with hyphens).

Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

How to get:
1. Create session: Use session tool with action='create'
2. Find session: Use semantic_search to search for previous sessions

Example: "60c598e2-d602-4e07-a328-c458006d48c7"

Note: Must be a string, not a number. UUIDs are always strings."#)]
    pub session_id: String,
    /// Interaction type for single-update mode.
    #[schemars(
        description = r#"Type of interaction for single update (must be exact lowercase value).

Valid values:
- qa: Questions and answers about the codebase
- decision_made: Architectural decisions and trade-offs
- problem_solved: Bug fixes and solutions to technical problems
- code_change: Code modifications and refactoring
- requirement_added: New requirements or constraints
- concept_defined: Technical concepts and patterns explained

Note: Required for single update mode (when 'updates' array is not provided)."#
    )]
    pub interaction_type: Option<String>,
    /// Key-value content for single-update mode.
    #[schemars(
        description = r#"Content as key-value pairs for single update (all values must be strings).

Format: HashMap<String, String> where both keys and values are strings.

Examples:
Simple: {"decision": "Use Rust", "rationale": "Performance"}
Complex: {"criteria": "{\"performance\": 9}", "date": "2025-01-12"}

Note: For complex nested data, stringify as JSON first. Required for single update mode."#
    )]
    pub content: Option<std::collections::HashMap<String, String>>,
    /// Named entities mentioned in this update (single-update mode).
    /// Required by the canonical write path.
    #[serde(default)]
    #[schemars(description = r#"Named entities mentioned in this update.

Each entry: {"name": "<unique name>", "entity_type": "concept|technology|problem|solution|decision|code_component"}.
Required (must contain at least one entity) so the entity graph captures every write."#)]
    pub entities: Vec<post_cortex_mcp::EntityItem>,
    /// Relations between the entities listed above (single-update mode).
    #[serde(default)]
    #[schemars(description = r#"Relations between the entities listed above.

Each entry: {"from_entity": "<name>", "to_entity": "<name>", "relation_type": "depends_on|implements|caused_by|leads_to|related_to|required_by|conflicts_with|solves", "context": "<short why>"}.
Required (must contain at least one relation). Both endpoints must appear in the `entities` array; self-relations and dangling references are rejected with InvalidArgument."#)]
    pub relations: Vec<post_cortex_mcp::RelationItem>,
    /// Optional code reference for single-update mode.
    #[schemars(description = r#"Optional code reference for single update.

Can be a simple string or complex object:

Examples:
- Simple: "src/main.rs:42"
- Complex: {"file": "src/main.rs", "line": 42, "function": "process_data"}"#)]
    pub code_reference: Option<serde_json::Value>,
    /// Bulk update array; overrides single-update fields when present.
    #[schemars(
        description = r#"Array of updates for bulk operation (overrides single fields if provided).

Use this mode to add multiple updates at once.

Format: Array of objects, each with interaction_type and content.

Example:
{
  "session_id": "60c598e2-d602-4e07-a328-c458006d48c7",
  "updates": [
    {
      "interaction_type": "decision_made",
      "content": {"decision": "Use Rust", "rationale": "Performance"}
    },
    {
      "interaction_type": "code_change",
      "content": {"file": "main.rs", "change": "Add error handling"}
    }
  ]
}

Note: When provided, 'interaction_type' and 'content' fields are ignored."#
    )]
    pub updates: Option<Vec<ContextUpdateItem>>,
    /// Validate without persisting when `true`.
    #[schemars(
        description = r#"If true, validate the request without making any changes.

When dry_run is true, the request will be validated and a preview of what would happen will be returned, but no data will be stored.

Use cases:
- Test if your request format is correct
- Preview what would be stored
- Validate interaction_type and content structure
- Check if session exists

Example: {"dry_run": true, "session_id": "...", "interaction_type": "decision_made", "content": {...}}

Note: No changes are made to the session when dry_run is true."#
    )]
    pub dry_run: Option<bool>,
}

// =============================================================================
// Tool 3: semantic_search (unified)
// =============================================================================

/// Request payload for the `semantic_search` tool.
#[derive(Deserialize, JsonSchema, Debug)]
pub struct SemanticSearchRequest {
    /// Natural-language search query.
    #[schemars(description = r#"Search query for semantic search.

Examples:
- "How did we handle authentication?"
- "What were the performance issues?"
- "decision_made API design"

Note: Natural language queries work best. The search understands context."#)]
    pub query: String,
    /// Search scope (`session`, `workspace`, or `global`).
    #[schemars(
        description = r#"Search scope: 'session', 'workspace', or 'global' (default: 'global').

Valid values:
- session: Search within a specific session (requires scope_id)
- workspace: Search within a workspace (requires scope_id)
- global: Search across all data (default, no scope_id needed)

Examples:
✅ {"query": "performance", "scope": "global"} (default, searches everywhere)
✅ {"query": "auth", "scope": "session", "scope_id": "60c598e2-d602-4e07-a328-c458006d48c7"} (search specific session)
✅ {"query": "API", "scope": "workspace", "scope_id": "f1d2e3a4-b5c6-7d8e-9f0a-1b2c3d4e5f6f"} (search specific workspace)

Note: scope must be lowercase. When using 'session' or 'workspace', you must provide scope_id."#
    )]
    pub scope: Option<String>,
    /// UUID identifying the session or workspace to search.
    #[schemars(
        description = r#"Session ID or Workspace ID (required when scope is 'session' or 'workspace').

Format: 36-char UUID string (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)

When to use:
- Required when scope='session' (provide session UUID)
- Required when scope='workspace' (provide workspace UUID)
- Ignored when scope='global' or not specified

Examples:
✅ {"scope": "session", "scope_id": "60c598e2-d602-4e07-a328-c458006d48c7"} (correct)
❌ {"scope": "session"} (missing scope_id - will error)
❌ {"scope": "global", "scope_id": "..."} (scope_id ignored for global)

Note: Must be a valid UUID string. Use 'session' or 'semantic_search' tools to find UUIDs."#
    )]
    pub scope_id: Option<String>,
    /// Maximum number of results to return.
    #[schemars(
        description = r#"Maximum number of results to return (default: 10, max: 100).

Examples:
- 5: Return top 5 most relevant results
- 20: Return top 20 results
- null or omit: Use default of 10

Note: Higher values may slow down search. Maximum allowed is 100."#
    )]
    pub limit: Option<usize>,
    /// ISO 8601 lower bound for date filtering.
    #[schemars(
        description = r#"Filter results from this date onwards (ISO 8601 format).

Format: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SSZ

Examples:
- "2025-01-01": Results from January 1st, 2025 onwards
- "2025-01-15T10:00:00Z": Results from January 15th, 2025 at 10am UTC onwards

Note: Use with date_to to specify a date range."#
    )]
    pub date_from: Option<String>,
    /// ISO 8601 upper bound for date filtering.
    #[schemars(description = r#"Filter results up to this date (ISO 8601 format).

Format: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SSZ

Examples:
- "2025-01-31": Results up to January 31st, 2025
- "2025-01-15T10:00:00Z": Results up to January 15th, 2025 at 10am UTC

Note: Use with date_from to specify a date range."#)]
    pub date_to: Option<String>,
    /// Filter results to these interaction types.
    #[schemars(description = r#"Filter by interaction types (array of strings).

Valid types:
- qa: Questions and answers
- decision_made: Architectural decisions
- problem_solved: Bug fixes and solutions
- code_change: Code modifications
- requirement_added: New requirements
- concept_defined: Technical concepts

Examples:
- ["decision_made"]: Only show decisions
- ["decision_made", "problem_solved"]: Show decisions and solutions
- null or omit: Show all interaction types

Note: Must be exact lowercase values. Filters reduce results to only these types."#)]
    pub interaction_type: Option<Vec<String>>,
    /// Temporal decay factor favouring recent content.
    #[schemars(
        description = r#"Temporal decay factor to prioritize recent content (default: 0.0 = disabled).

Valid values:
- 0.0: Disabled - pure relevance ranking (default, backward compatible)
- 0.1 - 0.5: Soft bias toward recent content
- 0.5 - 1.0: Moderate bias toward recent content
- 1.0+: Aggressive bias toward recent content

How it works:
- Uses exponential decay: score × e^(-λ × days/365)
- Older content gets progressively lower scores
- Fresh content (1 day) retains ~99.9% score at λ=0.5
- Year-old content retains ~60.6% score at λ=0.5, ~36.8% at λ=1.0

When to use:
- Debugging recent issues: 0.5 - 1.0
- Finding latest solutions: 0.3 - 0.7
- Architecture docs (timeless): 0.0 or omit
- Current context: 1.0+

Examples:
✅ {"query": "timeout error", "recency_bias": 0.5} (recent bugs prioritized)
✅ {"query": "authentication", "recency_bias": 0.0} (all docs equal)
✅ {"query": "performance", "recency_bias": 1.0} (very recent)

Note: Only affects ranking order, doesn't filter out old results."#
    )]
    pub recency_bias: Option<f32>,
}

// =============================================================================
// Tool 4: get_structured_summary (extended)
// =============================================================================

/// Request payload for the `get_structured_summary` tool.
#[derive(Deserialize, JsonSchema, Debug)]
pub struct GetStructuredSummaryRequest {
    /// Session UUID to summarize.
    #[schemars(description = "Session ID")]
    pub session_id: String,
    /// Sections to include (`decisions`, `insights`, `entities`, `all`).
    #[schemars(
        description = "Sections to include: decisions, insights, entities, all (default: all)"
    )]
    pub include: Option<Vec<String>>,
    /// Maximum number of decisions to include.
    #[schemars(description = "Maximum decisions to include")]
    pub decisions_limit: Option<usize>,
    /// Maximum number of entities to include.
    #[schemars(description = "Maximum entities to include")]
    pub entities_limit: Option<usize>,
    /// Maximum number of questions to include.
    #[schemars(description = "Maximum questions to include")]
    pub questions_limit: Option<usize>,
    /// Maximum number of concepts to include.
    #[schemars(description = "Maximum concepts to include")]
    pub concepts_limit: Option<usize>,
    /// Minimum confidence level for included items.
    #[schemars(description = "Minimum confidence level")]
    pub min_confidence: Option<f32>,
    /// Whether to use compact output format.
    #[schemars(description = "Use compact format for large sessions")]
    pub compact: Option<bool>,
}

// =============================================================================
// Tool 5: query_conversation_context
// =============================================================================

/// Request payload for the `query_conversation_context` tool.
#[derive(Deserialize, JsonSchema, Debug)]
pub struct QueryConversationContextRequest {
    /// Session UUID to query.
    #[schemars(description = "Session ID")]
    pub session_id: String,
    /// Query type selector.
    #[schemars(
        description = "Query type. Supported: find_related_entities, get_entity_context, search_updates, entity_importance, entity_network, find_related_content (topic, max_results), key_decisions, key_insights (limit), session_statistics, structured_summary, decisions, open_questions, related_entities, all_entities, trace_relationships, get_most_important_entities, get_recently_mentioned_entities, analyze_entity_importance, find_entities_by_type, assemble_context, recent_changes, code_references."
    )]
    pub query_type: String,
    /// Key-value query parameters.
    #[schemars(description = "Query parameters as key-value pairs")]
    pub parameters: std::collections::HashMap<String, String>,
}

// =============================================================================
// Tool 6: manage_workspace
// =============================================================================

/// Request payload for the `manage_workspace` tool.
#[derive(Deserialize, JsonSchema, Debug)]
pub struct ManageWorkspaceRequest {
    /// Workspace action (`create`, `list`, `get`, `delete`, `add_session`, `remove_session`).
    #[schemars(description = r#"Action to perform on workspaces.

Valid values:
- create: Create a new workspace (requires name and description)
- list: List all workspaces
- get: Get workspace details (requires workspace_id)
- delete: Delete a workspace (requires workspace_id)
- add_session: Add a session to workspace (requires workspace_id, session_id, optional role)
- remove_session: Remove a session from workspace (requires workspace_id, session_id)

Examples:
✅ {"action": "create", "name": "Auth Feature", "description": "OAuth2 work"}
✅ {"action": "get", "workspace_id": "f1d2e3a4-..."}
✅ {"action": "add_session", "workspace_id": "...", "session_id": "...", "role": "primary"}

Note: Must be lowercase. Different actions require different parameters."#)]
    pub action: String,
    /// Workspace UUID for actions that target a specific workspace.
    #[schemars(
        description = r#"Workspace ID (36-char UUID) for get/delete/add_session/remove_session actions.

Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

When to use:
- Required for: get, delete, add_session, remove_session
- Not used for: create, list

How to get:
- Use manage_workspace with action='list' to see all workspaces
- Use semantic_search to find workspaces by name

Example: "f1d2e3a4-b5c6-7d8e-9f0a-1b2c3d4e5f6f"

Note: Must be a valid UUID string. Not a number."#
    )]
    pub workspace_id: Option<String>,
    /// Session UUID for `add_session`/`remove_session`.
    #[schemars(
        description = r#"Session ID (36-char UUID) for add_session/remove_session actions.

Format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

When to use:
- Required for: add_session, remove_session
- Not used for: create, list, get, delete

How to get:
- Use session tool with action='list' to see all sessions
- Use semantic_search to find sessions

Example: "60c598e2-d602-4e07-a328-c458006d48c7"

Note: Must be a valid UUID string. Not a number."#
    )]
    pub session_id: Option<String>,
    /// Workspace name (used by `create`).
    #[schemars(description = r#"Workspace name (for create action).

Example: "Authentication Feature" or "API Design"

Note: Only used when action='create'. Helps identify the workspace."#)]
    pub name: Option<String>,
    /// Workspace description (used by `create`).
    #[schemars(description = r#"Workspace description (for create action).

Example: "Working on OAuth2 authentication and user management"

Note: Only used when action='create'. Provides context for the workspace."#)]
    pub description: Option<String>,
    /// Session role within the workspace (used by `add_session`).
    #[schemars(
        description = r#"Session role in workspace (for add_session action, default: 'related').

Valid values:
- primary: Main session for this workspace
- related: Related context session
- dependency: Required dependency session
- shared: Shared reference session

Examples:
✅ {"action": "add_session", "workspace_id": "...", "session_id": "...", "role": "primary"}
✅ {"action": "add_session", "workspace_id": "...", "session_id": "...", "role": "related"}
✅ {"action": "add_session", "workspace_id": "...", "session_id": "..."} (defaults to "related")

Note: Only used when action='add_session'. Defaults to 'related' if omitted."#
    )]
    pub role: Option<String>,
}