summaryrefslogtreecommitdiff
path: root/makima/src
diff options
context:
space:
mode:
authorsoryu <soryu@soryu.co>2026-02-12 02:29:45 +0000
committerGitHub <noreply@github.com>2026-02-12 02:29:45 +0000
commit355f10964c4dbec24a244a00caba5c17ed23fc65 (patch)
tree6fdc998e6b95948e80a87a962acd58acf79d5b98 /makima/src
parent9bd6eacaa9ebe860842b5d5cfbf2b7d2d0293ab1 (diff)
downloadsoryu-355f10964c4dbec24a244a00caba5c17ed23fc65.tar.gz
soryu-355f10964c4dbec24a244a00caba5c17ed23fc65.zip
makima: Add an optional memory system for directives (#59)
* feat: makima: Add an optional memory system for directives: Add directive_memories database table and migration * feat: makima: Add an optional memory system for directives: Update directive skill documentation with memory commands * feat: makima: Add an optional memory system for directives: Add repository functions for directive memory CRUD * feat: makima: Add an optional memory system for directives: Add frontend API functions and types for directive memory * feat: makima: Add an optional memory system for directives: Add Rust models for directive memory * WIP: heartbeat checkpoint * WIP: heartbeat checkpoint * WIP: heartbeat checkpoint * WIP: heartbeat checkpoint * feat: makima: Add an optional memory system for directives: Add memory panel to frontend DirectiveDetail component * Merge remote-tracking branch 'origin/makima/makima--add-an-optional-memory-system-for-directiv-5de1e06d' into combined branch * Merge remote-tracking branch 'origin/makima/makima--add-an-optional-memory-system-for-directiv-c8298c6c' into combined branch * feat: makima: Add an optional memory system for directives: Create useMultiTaskSubscription hook for multi-output WebSocket streaming * feat: makima: Add an optional memory system for directives: Create DirectiveLogStream component for stern-like multi-task output viewing * feat: makima: Add an optional memory system for directives: Integrate log stream panel into directive detail page
Diffstat (limited to 'makima/src')
-rw-r--r--makima/src/bin/makima.rs44
-rw-r--r--makima/src/daemon/api/directive.rs200
-rw-r--r--makima/src/daemon/cli/directive.rs48
-rw-r--r--makima/src/daemon/cli/mod.rs18
-rw-r--r--makima/src/daemon/skills/directive.md80
-rw-r--r--makima/src/db/models.rs47
-rw-r--r--makima/src/db/repository.rs146
-rw-r--r--makima/src/orchestration/directive.rs85
-rw-r--r--makima/src/server/handlers/directives.rs395
-rw-r--r--makima/src/server/mod.rs4
-rw-r--r--makima/src/server/openapi.rs21
11 files changed, 1068 insertions, 20 deletions
diff --git a/makima/src/bin/makima.rs b/makima/src/bin/makima.rs
index c2c9beb..d4af878 100644
--- a/makima/src/bin/makima.rs
+++ b/makima/src/bin/makima.rs
@@ -825,6 +825,50 @@ async fn run_directive(
.await?;
println!("{}", serde_json::to_string(&result.0)?);
}
+ DirectiveCommand::MemorySet(args) => {
+ let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
+ let result = client
+ .directive_memory_set(args.common.directive_id, &args.key, &args.value)
+ .await?;
+ println!("{}", serde_json::to_string(&result.0)?);
+ }
+ DirectiveCommand::MemoryGet(args) => {
+ let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
+ let result = client
+ .directive_memory_get(args.common.directive_id, &args.key)
+ .await?;
+ println!("{}", serde_json::to_string(&result.0)?);
+ }
+ DirectiveCommand::MemoryList(args) => {
+ let client = ApiClient::new(args.api_url, args.api_key)?;
+ let result = client
+ .directive_memory_list(args.directive_id)
+ .await?;
+ println!("{}", serde_json::to_string(&result.0)?);
+ }
+ DirectiveCommand::MemoryDelete(args) => {
+ let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
+ client
+ .directive_memory_delete(args.common.directive_id, &args.key)
+ .await?;
+ println!(r#"{{"success": true}}"#);
+ }
+ DirectiveCommand::MemoryClear(args) => {
+ let client = ApiClient::new(args.api_url, args.api_key)?;
+ client
+ .directive_memory_clear(args.directive_id)
+ .await?;
+ println!(r#"{{"success": true}}"#);
+ }
+ DirectiveCommand::MemoryBatchSet(args) => {
+ let client = ApiClient::new(args.common.api_url, args.common.api_key)?;
+ let entries: serde_json::Value = serde_json::from_str(&args.json)
+ .map_err(|e| format!("Invalid JSON: {}", e))?;
+ let result = client
+ .directive_memory_batch_set(args.common.directive_id, entries)
+ .await?;
+ println!("{}", serde_json::to_string(&result.0)?);
+ }
}
Ok(())
diff --git a/makima/src/daemon/api/directive.rs b/makima/src/daemon/api/directive.rs
index 5886766..fcc2ca5 100644
--- a/makima/src/daemon/api/directive.rs
+++ b/makima/src/daemon/api/directive.rs
@@ -30,6 +30,54 @@ pub struct UpdateStepDepsRequest {
pub depends_on: Vec<Uuid>,
}
+/// Percent-encode a string for use as a URL path segment.
+///
+/// Encodes all characters except unreserved characters (alphanumeric, `-`, `.`, `_`, `~`).
+fn percent_encode_path(s: &str) -> String {
+ let mut encoded = String::with_capacity(s.len());
+ for byte in s.bytes() {
+ match byte {
+ b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~' => {
+ encoded.push(byte as char);
+ }
+ _ => {
+ encoded.push_str(&format!("%{:02X}", byte));
+ }
+ }
+ }
+ encoded
+}
+
+/// Request body for setting a single memory entry.
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct SetMemoryRequest {
+ pub key: String,
+ pub value: String,
+}
+
+/// A single entry within a batch set request.
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BatchMemoryEntry {
+ pub key: String,
+ pub value: String,
+}
+
+/// Request body for setting multiple memory entries at once.
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BatchSetMemoryRequest {
+ pub entries: Vec<BatchMemoryEntry>,
+}
+
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct MemorySetRequest {
+ pub value: String,
+}
+
+
impl ApiClient {
/// List all directives.
pub async fn list_directives(&self) -> Result<JsonValue, ApiError> {
@@ -145,6 +193,158 @@ impl ApiClient {
let req = UpdateDirectiveMetadataRequest { pr_url, pr_branch };
self.put(&format!("/api/v1/directives/{}", directive_id), &req).await
}
+
+ // ── Directive Memory ──────────────────────────────────────────────
+
+ /// List all memory entries for a directive.
+ pub async fn list_memories(&self, directive_id: Uuid) -> Result<JsonValue, ApiError> {
+ self.get(&format!("/api/v1/directives/{}/memory", directive_id))
+ .await
+ }
+
+ /// Get a single memory entry by key.
+ pub async fn get_memory(
+ &self,
+ directive_id: Uuid,
+ key: &str,
+ ) -> Result<JsonValue, ApiError> {
+ self.get(&format!(
+ "/api/v1/directives/{}/memory/{}",
+ directive_id,
+ percent_encode_path(key)
+ ))
+ .await
+ }
+
+ /// Set (create or update) a single memory entry.
+ pub async fn set_memory(
+ &self,
+ directive_id: Uuid,
+ key: &str,
+ value: &str,
+ ) -> Result<JsonValue, ApiError> {
+ let req = SetMemoryRequest {
+ key: key.to_string(),
+ value: value.to_string(),
+ };
+ self.put(&format!("/api/v1/directives/{}/memory", directive_id), &req)
+ .await
+ }
+
+ /// Set multiple memory entries in a single request.
+ pub async fn batch_set_memories(
+ &self,
+ directive_id: Uuid,
+ entries: Vec<(String, String)>,
+ ) -> Result<JsonValue, ApiError> {
+ let req = BatchSetMemoryRequest {
+ entries: entries
+ .into_iter()
+ .map(|(key, value)| BatchMemoryEntry { key, value })
+ .collect(),
+ };
+ self.post(
+ &format!("/api/v1/directives/{}/memory/batch", directive_id),
+ &req,
+ )
+ .await
+ }
+
+ /// Delete a single memory entry by key.
+ pub async fn delete_memory(
+ &self,
+ directive_id: Uuid,
+ key: &str,
+ ) -> Result<(), ApiError> {
+ self.delete(&format!(
+ "/api/v1/directives/{}/memory/{}",
+ directive_id,
+ percent_encode_path(key)
+ ))
+ .await
+ }
+
+ /// Clear all memory entries for a directive.
+ pub async fn clear_memories(&self, directive_id: Uuid) -> Result<(), ApiError> {
+ self.delete(&format!("/api/v1/directives/{}/memory", directive_id))
+ .await
+ }
+
+ // ── CLI-facing Directive Memory aliases ──────────────────────────
+
+ /// Set a memory key-value pair for a directive (CLI-facing).
+ pub async fn directive_memory_set(
+ &self,
+ directive_id: Uuid,
+ key: &str,
+ value: &str,
+ ) -> Result<JsonValue, ApiError> {
+ let req = MemorySetRequest {
+ value: value.to_string(),
+ };
+ self.put(
+ &format!("/api/v1/directives/{}/memory/{}", directive_id, key),
+ &req,
+ )
+ .await
+ }
+
+ /// Get a memory value by key for a directive (CLI-facing).
+ pub async fn directive_memory_get(
+ &self,
+ directive_id: Uuid,
+ key: &str,
+ ) -> Result<JsonValue, ApiError> {
+ self.get(&format!(
+ "/api/v1/directives/{}/memory/{}",
+ directive_id, key
+ ))
+ .await
+ }
+
+ /// List all memory key-value pairs for a directive (CLI-facing).
+ pub async fn directive_memory_list(
+ &self,
+ directive_id: Uuid,
+ ) -> Result<JsonValue, ApiError> {
+ self.get(&format!("/api/v1/directives/{}/memory", directive_id))
+ .await
+ }
+
+ /// Delete a memory key for a directive (CLI-facing).
+ pub async fn directive_memory_delete(
+ &self,
+ directive_id: Uuid,
+ key: &str,
+ ) -> Result<(), ApiError> {
+ self.delete(&format!(
+ "/api/v1/directives/{}/memory/{}",
+ directive_id, key
+ ))
+ .await
+ }
+
+ /// Clear all memory for a directive (CLI-facing).
+ pub async fn directive_memory_clear(
+ &self,
+ directive_id: Uuid,
+ ) -> Result<(), ApiError> {
+ self.delete(&format!("/api/v1/directives/{}/memory", directive_id))
+ .await
+ }
+
+ /// Batch set multiple memory key-value pairs for a directive (CLI-facing).
+ pub async fn directive_memory_batch_set(
+ &self,
+ directive_id: Uuid,
+ entries: serde_json::Value,
+ ) -> Result<JsonValue, ApiError> {
+ self.post(
+ &format!("/api/v1/directives/{}/memory/batch", directive_id),
+ &entries,
+ )
+ .await
+ }
}
#[derive(Serialize)]
diff --git a/makima/src/daemon/cli/directive.rs b/makima/src/daemon/cli/directive.rs
index 2e6ac1d..8eded77 100644
--- a/makima/src/daemon/cli/directive.rs
+++ b/makima/src/daemon/cli/directive.rs
@@ -125,3 +125,51 @@ pub struct UpdateArgs {
#[arg(long)]
pub pr_branch: Option<String>,
}
+
+/// Arguments for memory-set command.
+#[derive(Args, Debug)]
+pub struct MemorySetArgs {
+ #[command(flatten)]
+ pub common: DirectiveArgs,
+
+ /// Memory key
+ pub key: String,
+
+ /// Memory value
+ pub value: String,
+}
+
+/// Arguments for memory-get command.
+#[derive(Args, Debug)]
+pub struct MemoryGetArgs {
+ #[command(flatten)]
+ pub common: DirectiveArgs,
+
+ /// Memory key
+ pub key: String,
+}
+
+/// Arguments for memory-list command (uses DirectiveArgs directly).
+
+/// Arguments for memory-delete command.
+#[derive(Args, Debug)]
+pub struct MemoryDeleteArgs {
+ #[command(flatten)]
+ pub common: DirectiveArgs,
+
+ /// Memory key to delete
+ pub key: String,
+}
+
+/// Arguments for memory-clear command (uses DirectiveArgs directly).
+
+/// Arguments for memory-batch-set command.
+#[derive(Args, Debug)]
+pub struct MemoryBatchSetArgs {
+ #[command(flatten)]
+ pub common: DirectiveArgs,
+
+ /// JSON object of key-value pairs: {"key1":"value1","key2":"value2"}
+ #[arg(long)]
+ pub json: String,
+}
diff --git a/makima/src/daemon/cli/mod.rs b/makima/src/daemon/cli/mod.rs
index bcaaa70..a78e5f8 100644
--- a/makima/src/daemon/cli/mod.rs
+++ b/makima/src/daemon/cli/mod.rs
@@ -249,6 +249,24 @@ pub enum DirectiveCommand {
/// Update directive metadata (PR URL, etc.)
Update(directive::UpdateArgs),
+
+ /// Set a memory key-value pair for the directive
+ MemorySet(directive::MemorySetArgs),
+
+ /// Get a memory value by key
+ MemoryGet(directive::MemoryGetArgs),
+
+ /// List all memory key-value pairs
+ MemoryList(DirectiveArgs),
+
+ /// Delete a memory key
+ MemoryDelete(directive::MemoryDeleteArgs),
+
+ /// Clear all memory for the directive
+ MemoryClear(DirectiveArgs),
+
+ /// Batch set multiple memory key-value pairs from JSON
+ MemoryBatchSet(directive::MemoryBatchSetArgs),
}
impl Cli {
diff --git a/makima/src/daemon/skills/directive.md b/makima/src/daemon/skills/directive.md
index 7c55cf8..68d9277 100644
--- a/makima/src/daemon/skills/directive.md
+++ b/makima/src/daemon/skills/directive.md
@@ -76,6 +76,86 @@ Updates the goal and bumps `goalUpdatedAt`. If the directive is `idle`, it react
makima directive pause
```
+## Memory Commands
+
+Directives have an optional key-value memory system that persists across steps and planning cycles. Use memory to share context, decisions, and learned information between steps — so downstream tasks don't need to re-discover what earlier steps already figured out.
+
+### Set a Memory Entry
+```bash
+makima directive memory-set <key> <value>
+```
+Stores a key-value pair in the directive's memory. If the key already exists, the value is overwritten. Keys are strings; values are strings (use JSON encoding for structured data).
+
+**Example:**
+```bash
+makima directive memory-set "db_schema_version" "3"
+makima directive memory-set "auth_pattern" "JWT with refresh tokens stored in httpOnly cookies"
+makima directive memory-set "api_base_path" "/api/v2"
+```
+
+### Get a Memory Entry
+```bash
+makima directive memory-get <key>
+```
+Retrieves the value for a specific key. Returns the value if found, or an error if the key does not exist.
+
+**Example:**
+```bash
+makima directive memory-get "db_schema_version"
+```
+
+### List All Memory Entries
+```bash
+makima directive memory-list
+```
+Returns all key-value pairs stored in the directive's memory. Useful for understanding what context is available before starting work on a step.
+
+### Delete a Memory Entry
+```bash
+makima directive memory-delete <key>
+```
+Removes a single key-value pair from memory.
+
+**Example:**
+```bash
+makima directive memory-delete "deprecated_config_key"
+```
+
+### Clear All Memory
+```bash
+makima directive memory-clear
+```
+Removes **all** key-value pairs from the directive's memory. Use with caution — this is irreversible.
+
+### Batch Set Memory Entries
+```bash
+makima directive memory-batch-set --json '{"key1": "value1", "key2": "value2"}'
+```
+Sets multiple key-value pairs in a single operation. Existing keys are overwritten; keys not mentioned are left unchanged.
+
+**Example:**
+```bash
+makima directive memory-batch-set --json '{"framework": "axum", "orm": "sqlx", "test_runner": "cargo test"}'
+```
+
+## Using Memory Effectively
+
+### When to Write Memory
+- **During planning**: Record architectural decisions, technology choices, and file layout patterns
+- **After step completion**: Save discovered information (e.g., generated IDs, API endpoints, schema details)
+- **When context matters**: Store anything a downstream step would need to avoid re-exploring the codebase
+
+### When to Read Memory
+- **At step start**: Check `memory-list` to see what context previous steps have provided
+- **Before making decisions**: Check if an earlier step already made a relevant architectural choice
+- **During re-planning**: Read memory to understand what was learned in previous iterations
+
+### Best Practices
+- Use descriptive, namespaced keys (e.g., `auth.strategy`, `db.migration_count`, `api.base_url`)
+- Store concise but complete values — enough for another task to act on without guessing
+- Clean up stale entries when the directive's goal changes significantly
+- Use `memory-batch-set` when recording multiple related decisions at once
+
## Orchestration Workflow
### Initial Setup
diff --git a/makima/src/db/models.rs b/makima/src/db/models.rs
index 542339f..169f468 100644
--- a/makima/src/db/models.rs
+++ b/makima/src/db/models.rs
@@ -2714,8 +2714,10 @@ pub struct Directive {
pub pr_url: Option<String>,
pub pr_branch: Option<String>,
pub completion_task_id: Option<Uuid>,
+ pub memory_enabled: bool,
pub goal_updated_at: DateTime<Utc>,
pub started_at: Option<DateTime<Utc>>,
+ pub memory_enabled: bool,
pub version: i32,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
@@ -2763,6 +2765,7 @@ pub struct DirectiveSummary {
pub orchestrator_task_id: Option<Uuid>,
pub pr_url: Option<String>,
pub completion_task_id: Option<Uuid>,
+ pub memory_enabled: bool,
pub version: i32,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
@@ -2789,6 +2792,8 @@ pub struct CreateDirectiveRequest {
pub repository_url: Option<String>,
pub local_path: Option<String>,
pub base_branch: Option<String>,
+ #[serde(default)]
+ pub memory_enabled: bool,
}
/// Request to update a directive.
@@ -2804,6 +2809,7 @@ pub struct UpdateDirectiveRequest {
pub orchestrator_task_id: Option<Uuid>,
pub pr_url: Option<String>,
pub pr_branch: Option<String>,
+ pub memory_enabled: Option<bool>,
pub version: Option<i32>,
}
@@ -2840,3 +2846,44 @@ pub struct UpdateDirectiveStepRequest {
pub task_id: Option<Uuid>,
pub order_index: Option<i32>,
}
+
+// =============================================================================
+// Directive Memory Types
+// =============================================================================
+
+/// A memory entry for a directive — key-value context that persists across tasks.
+#[derive(Debug, Clone, FromRow, Serialize, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct DirectiveMemory {
+ pub id: Uuid,
+ pub directive_id: Uuid,
+ pub key: String,
+ pub value: String,
+ pub category: Option<String>,
+ pub created_at: DateTime<Utc>,
+ pub updated_at: DateTime<Utc>,
+}
+
+/// Request to set a memory entry (upsert by key).
+#[derive(Debug, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct SetDirectiveMemoryRequest {
+ pub key: String,
+ pub value: String,
+ pub category: Option<String>,
+}
+
+/// Request to batch set memory entries.
+#[derive(Debug, Deserialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct BatchSetDirectiveMemoryRequest {
+ pub entries: Vec<SetDirectiveMemoryRequest>,
+}
+
+/// Response for listing memories.
+#[derive(Debug, Serialize, ToSchema)]
+#[serde(rename_all = "camelCase")]
+pub struct DirectiveMemoryListResponse {
+ pub memories: Vec<DirectiveMemory>,
+ pub total: i64,
+}
diff --git a/makima/src/db/repository.rs b/makima/src/db/repository.rs
index 7afbeea..95460f7 100644
--- a/makima/src/db/repository.rs
+++ b/makima/src/db/repository.rs
@@ -11,9 +11,10 @@ use super::models::{
ContractTypeTemplateRecord, ConversationMessage, ConversationSnapshot,
CreateContractRequest, CreateFileRequest, CreateTaskRequest,
CreateTemplateRequest, Daemon, DaemonTaskAssignment, DaemonWithCapacity,
- DeliverableDefinition, Directive, DirectiveStep, DirectiveSummary,
+ DeliverableDefinition, Directive, DirectiveMemory, DirectiveStep, DirectiveSummary,
CreateDirectiveRequest, CreateDirectiveStepRequest, UpdateDirectiveRequest,
- UpdateDirectiveStepRequest,
+ UpdateDirectiveStepRequest, SetDirectiveMemoryRequest,
+ BatchSetDirectiveMemoryRequest, DirectiveMemoryListResponse,
File, FileSummary, FileVersion, HistoryEvent, HistoryQueryFilters,
MeshChatConversation, MeshChatMessageRecord, PhaseChangeResult, PhaseConfig,
PhaseDefinition, SupervisorHeartbeatRecord, SupervisorState,
@@ -4929,8 +4930,8 @@ pub async fn create_directive_for_owner(
) -> Result<Directive, sqlx::Error> {
sqlx::query_as::<_, Directive>(
r#"
- INSERT INTO directives (owner_id, title, goal, repository_url, local_path, base_branch)
- VALUES ($1, $2, $3, $4, $5, $6)
+ INSERT INTO directives (owner_id, title, goal, repository_url, local_path, base_branch, memory_enabled)
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING *
"#,
)
@@ -4940,6 +4941,7 @@ pub async fn create_directive_for_owner(
.bind(&req.repository_url)
.bind(&req.local_path)
.bind(&req.base_branch)
+ .bind(req.memory_enabled)
.fetch_one(pool)
.await
}
@@ -4992,7 +4994,7 @@ pub async fn list_directives_for_owner(
SELECT
d.id, d.owner_id, d.title, d.goal, d.status, d.repository_url,
d.orchestrator_task_id, d.pr_url, d.completion_task_id,
- d.version, d.created_at, d.updated_at,
+ d.memory_enabled, d.version, d.created_at, d.updated_at,
COALESCE((SELECT COUNT(*) FROM directive_steps WHERE directive_id = d.id), 0) as total_steps,
COALESCE((SELECT COUNT(*) FROM directive_steps WHERE directive_id = d.id AND status = 'completed'), 0) as completed_steps,
COALESCE((SELECT COUNT(*) FROM directive_steps WHERE directive_id = d.id AND status = 'running'), 0) as running_steps,
@@ -5046,12 +5048,13 @@ pub async fn update_directive_for_owner(
let orchestrator_task_id = req.orchestrator_task_id.or(current.orchestrator_task_id);
let pr_url = req.pr_url.as_deref().or(current.pr_url.as_deref());
let pr_branch = req.pr_branch.as_deref().or(current.pr_branch.as_deref());
+ let memory_enabled = req.memory_enabled.unwrap_or(current.memory_enabled);
let result = sqlx::query_as::<_, Directive>(
r#"
UPDATE directives
SET title = $3, goal = $4, status = $5, repository_url = $6, local_path = $7,
- base_branch = $8, orchestrator_task_id = $9, pr_url = $10, pr_branch = $11,
+ base_branch = $8, orchestrator_task_id = $9, pr_url = $10, pr_branch = $11, memory_enabled = $12,
version = version + 1, updated_at = NOW()
WHERE id = $1 AND owner_id = $2
RETURNING *
@@ -5068,6 +5071,7 @@ pub async fn update_directive_for_owner(
.bind(orchestrator_task_id)
.bind(pr_url)
.bind(pr_branch)
+ .bind(memory_enabled)
.fetch_optional(pool)
.await
.map_err(RepositoryError::Database)?;
@@ -5500,6 +5504,7 @@ pub struct StepForDispatch {
pub directive_title: String,
pub repository_url: Option<String>,
pub base_branch: Option<String>,
+ pub memory_enabled: bool,
}
/// Get ready steps that need task dispatch.
@@ -5519,7 +5524,8 @@ pub async fn get_ready_steps_for_dispatch(
d.owner_id,
d.title AS directive_title,
d.repository_url,
- d.base_branch
+ d.base_branch,
+ d.memory_enabled
FROM directive_steps ds
JOIN directives d ON d.id = ds.directive_id
WHERE ds.status = 'ready'
@@ -5740,3 +5746,129 @@ pub async fn get_directive_max_generation(
.await?;
Ok(row.0.unwrap_or(0))
}
+
+// =============================================================================
+// Directive Memory CRUD
+// =============================================================================
+
+/// List all memories for a directive, optionally filtered by category.
+pub async fn list_directive_memories(
+ pool: &PgPool,
+ directive_id: Uuid,
+ category: Option<&str>,
+) -> Result<Vec<DirectiveMemory>, sqlx::Error> {
+ match category {
+ Some(cat) => {
+ sqlx::query_as::<_, DirectiveMemory>(
+ r#"
+ SELECT * FROM directive_memories
+ WHERE directive_id = $1 AND category = $2
+ ORDER BY key
+ "#,
+ )
+ .bind(directive_id)
+ .bind(cat)
+ .fetch_all(pool)
+ .await
+ }
+ None => {
+ sqlx::query_as::<_, DirectiveMemory>(
+ r#"
+ SELECT * FROM directive_memories
+ WHERE directive_id = $1
+ ORDER BY key
+ "#,
+ )
+ .bind(directive_id)
+ .fetch_all(pool)
+ .await
+ }
+ }
+}
+
+/// Get a single memory entry by directive ID and key.
+pub async fn get_directive_memory(
+ pool: &PgPool,
+ directive_id: Uuid,
+ key: &str,
+) -> Result<Option<DirectiveMemory>, sqlx::Error> {
+ sqlx::query_as::<_, DirectiveMemory>(
+ r#"
+ SELECT * FROM directive_memories
+ WHERE directive_id = $1 AND key = $2
+ "#,
+ )
+ .bind(directive_id)
+ .bind(key)
+ .fetch_optional(pool)
+ .await
+}
+
+/// Set (upsert) a memory entry for a directive.
+pub async fn set_directive_memory(
+ pool: &PgPool,
+ directive_id: Uuid,
+ req: &SetDirectiveMemoryRequest,
+) -> Result<DirectiveMemory, sqlx::Error> {
+ sqlx::query_as::<_, DirectiveMemory>(
+ r#"
+ INSERT INTO directive_memories (directive_id, key, value, category)
+ VALUES ($1, $2, $3, $4)
+ ON CONFLICT (directive_id, key)
+ DO UPDATE SET value = EXCLUDED.value,
+ category = EXCLUDED.category,
+ updated_at = NOW()
+ RETURNING *
+ "#,
+ )
+ .bind(directive_id)
+ .bind(&req.key)
+ .bind(&req.value)
+ .bind(&req.category)
+ .fetch_one(pool)
+ .await
+}
+
+/// Batch set memory entries for a directive.
+pub async fn batch_set_directive_memories(
+ pool: &PgPool,
+ directive_id: Uuid,
+ memories: &[SetDirectiveMemoryRequest],
+) -> Result<Vec<DirectiveMemory>, sqlx::Error> {
+ let mut results = Vec::with_capacity(memories.len());
+ for mem in memories {
+ let result = set_directive_memory(pool, directive_id, mem).await?;
+ results.push(result);
+ }
+ Ok(results)
+}
+
+/// Delete a single memory entry by key.
+pub async fn delete_directive_memory(
+ pool: &PgPool,
+ directive_id: Uuid,
+ key: &str,
+) -> Result<bool, sqlx::Error> {
+ let result = sqlx::query(
+ r#"DELETE FROM directive_memories WHERE directive_id = $1 AND key = $2"#,
+ )
+ .bind(directive_id)
+ .bind(key)
+ .execute(pool)
+ .await?;
+ Ok(result.rows_affected() > 0)
+}
+
+/// Delete all memory entries for a directive.
+pub async fn clear_directive_memories(
+ pool: &PgPool,
+ directive_id: Uuid,
+) -> Result<u64, sqlx::Error> {
+ let result = sqlx::query(
+ r#"DELETE FROM directive_memories WHERE directive_id = $1"#,
+ )
+ .bind(directive_id)
+ .execute(pool)
+ .await?;
+ Ok(result.rows_affected())
+}
diff --git a/makima/src/orchestration/directive.rs b/makima/src/orchestration/directive.rs
index 15cc7ed..cb3983a 100644
--- a/makima/src/orchestration/directive.rs
+++ b/makima/src/orchestration/directive.rs
@@ -9,7 +9,7 @@
use sqlx::PgPool;
use uuid::Uuid;
-use crate::db::models::{CreateTaskRequest, UpdateTaskRequest};
+use crate::db::models::{CreateTaskRequest, DirectiveMemory, UpdateTaskRequest};
use crate::db::repository;
use crate::server::state::{DaemonCommand, SharedState};
@@ -44,7 +44,24 @@ impl DirectiveOrchestrator {
"Directive needs planning — spawning planning task"
);
- let plan = build_planning_prompt(&directive, &[], 1);
+ // Load memories if memory is enabled for this directive
+ let memories = if directive.memory_enabled {
+ match repository::list_directive_memories(&self.pool, directive.id).await {
+ Ok(m) => m,
+ Err(e) => {
+ tracing::warn!(
+ directive_id = %directive.id,
+ error = %e,
+ "Failed to load directive memories for planning — continuing without"
+ );
+ vec![]
+ }
+ }
+ } else {
+ vec![]
+ };
+
+ let plan = build_planning_prompt(&directive, &[], 1, &memories);
if let Err(e) = self
.spawn_orchestrator_task(
@@ -86,17 +103,40 @@ impl DirectiveOrchestrator {
.as_deref()
.unwrap_or("Execute the step described below.");
+ // Load memories if memory is enabled for this directive
+ let memory_context = if step.memory_enabled {
+ match repository::list_directive_memories(&self.pool, step.directive_id).await {
+ Ok(memories) if !memories.is_empty() => {
+ format!("\n\nMEMORY CONTEXT (from previous planning/execution cycles):\n{}\n",
+ format_memories_for_prompt(&memories))
+ }
+ Ok(_) => String::new(),
+ Err(e) => {
+ tracing::warn!(
+ directive_id = %step.directive_id,
+ error = %e,
+ "Failed to load directive memories for execution — continuing without"
+ );
+ String::new()
+ }
+ }
+ } else {
+ String::new()
+ };
+
let plan = format!(
"You are executing a step in directive \"{directive_title}\".\n\n\
STEP: {step_name}\n\
DESCRIPTION: {description}\n\n\
- INSTRUCTIONS:\n{task_plan}\n\n\
+ INSTRUCTIONS:\n{task_plan}\n\
+ {memory_context}\
When done, the system will automatically mark this step as completed.\n\
If you cannot complete the task, report the failure clearly.",
directive_title = step.directive_title,
step_name = step.step_name,
description = step.step_description.as_deref().unwrap_or("(none)"),
task_plan = task_plan,
+ memory_context = memory_context,
);
match self
@@ -239,7 +279,24 @@ impl DirectiveOrchestrator {
let generation =
repository::get_directive_max_generation(&self.pool, directive.id).await? + 1;
- let plan = build_planning_prompt(&directive, &existing_steps, generation);
+ // Load memories if memory is enabled for this directive
+ let memories = if directive.memory_enabled {
+ match repository::list_directive_memories(&self.pool, directive.id).await {
+ Ok(m) => m,
+ Err(e) => {
+ tracing::warn!(
+ directive_id = %directive.id,
+ error = %e,
+ "Failed to load directive memories for re-planning — continuing without"
+ );
+ vec![]
+ }
+ }
+ } else {
+ vec![]
+ };
+
+ let plan = build_planning_prompt(&directive, &existing_steps, generation, &memories);
if let Err(e) = self
.spawn_orchestrator_task(
@@ -597,14 +654,34 @@ impl DirectiveOrchestrator {
}
}
+/// Format memory entries into a readable prompt section.
+fn format_memories_for_prompt(memories: &[DirectiveMemory]) -> String {
+ let mut out = String::new();
+ for memory in memories {
+ out.push_str(&format!(
+ "- [{}] ({}): {}\n",
+ memory.category, memory.source, memory.content
+ ));
+ }
+ out
+}
+
/// Build the planning prompt for a directive.
fn build_planning_prompt(
directive: &crate::db::models::Directive,
existing_steps: &[crate::db::models::DirectiveStep],
generation: i32,
+ memories: &[DirectiveMemory],
) -> String {
let mut prompt = String::new();
+ // Include memory context if available
+ if !memories.is_empty() {
+ prompt.push_str("MEMORY CONTEXT (insights and decisions from previous cycles):\n");
+ prompt.push_str(&format_memories_for_prompt(memories));
+ prompt.push_str("\nUse these memories to inform your planning. Avoid repeating past mistakes and build on prior insights.\n\n");
+ }
+
if !existing_steps.is_empty() {
prompt.push_str(&format!(
"EXISTING STEPS (generation {}):\n",
diff --git a/makima/src/server/handlers/directives.rs b/makima/src/server/handlers/directives.rs
index d48ff74..f624d82 100644
--- a/makima/src/server/handlers/directives.rs
+++ b/makima/src/server/handlers/directives.rs
@@ -1,23 +1,31 @@
//! HTTP handlers for directive CRUD and DAG progression.
use axum::{
- extract::{Path, State},
+ extract::{Path, Query, State},
http::StatusCode,
response::IntoResponse,
Json,
};
+use serde::Deserialize;
use uuid::Uuid;
use crate::db::models::{
- CreateDirectiveRequest, CreateDirectiveStepRequest, Directive, DirectiveListResponse,
- DirectiveStep, DirectiveWithSteps, UpdateDirectiveRequest, UpdateDirectiveStepRequest,
- UpdateGoalRequest,
+ BatchSetDirectiveMemoryRequest, CreateDirectiveRequest, CreateDirectiveStepRequest,
+ Directive, DirectiveListResponse, DirectiveMemory, DirectiveMemoryListResponse,
+ DirectiveStep, DirectiveWithSteps, SetDirectiveMemoryRequest, UpdateDirectiveRequest,
+ UpdateDirectiveStepRequest, UpdateGoalRequest,
};
use crate::db::repository;
use crate::server::auth::Authenticated;
use crate::server::messages::ApiError;
use crate::server::state::SharedState;
+/// Query parameters for the memory list endpoint.
+#[derive(Debug, Deserialize)]
+pub struct MemoryListQuery {
+ pub category: Option<String>,
+}
+
// =============================================================================
// Directive CRUD
// =============================================================================
@@ -839,3 +847,382 @@ pub async fn update_goal(
}
}
}
+
+// =============================================================================
+// Directive Memory CRUD
+// =============================================================================
+
+/// List all memories for a directive, optionally filtered by category.
+#[utoipa::path(
+ get,
+ path = "/api/v1/directives/{id}/memories",
+ params(
+ ("id" = Uuid, Path, description = "Directive ID"),
+ ("category" = Option<String>, Query, description = "Filter by category"),
+ ),
+ responses(
+ (status = 200, description = "List of memories", body = DirectiveMemoryListResponse),
+ (status = 404, description = "Directive not found", body = ApiError),
+ (status = 503, description = "Database not configured", body = ApiError),
+ ),
+ security(("bearer_auth" = []), ("api_key" = [])),
+ tag = "Directives"
+)]
+pub async fn list_memories(
+ State(state): State<SharedState>,
+ Authenticated(auth): Authenticated,
+ Path(id): Path<Uuid>,
+ Query(query): Query<MemoryListQuery>,
+) -> impl IntoResponse {
+ let Some(ref pool) = state.db_pool else {
+ return (
+ StatusCode::SERVICE_UNAVAILABLE,
+ Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
+ )
+ .into_response();
+ };
+
+ // Verify directive ownership
+ match repository::get_directive_for_owner(pool, auth.owner_id, id).await {
+ Ok(Some(_)) => {}
+ Ok(None) => {
+ return (
+ StatusCode::NOT_FOUND,
+ Json(ApiError::new("NOT_FOUND", "Directive not found")),
+ )
+ .into_response();
+ }
+ Err(e) => {
+ return (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Json(ApiError::new("GET_FAILED", &e.to_string())),
+ )
+ .into_response();
+ }
+ }
+
+ match repository::list_directive_memories(pool, id, query.category.as_deref()).await {
+ Ok(memories) => {
+ let total = memories.len() as i64;
+ Json(DirectiveMemoryListResponse { memories, total }).into_response()
+ }
+ Err(e) => {
+ tracing::error!("Failed to list memories: {}", e);
+ (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Json(ApiError::new("LIST_FAILED", &e.to_string())),
+ )
+ .into_response()
+ }
+ }
+}
+
+/// Get a single memory entry by key.
+#[utoipa::path(
+ get,
+ path = "/api/v1/directives/{id}/memories/{key}",
+ params(
+ ("id" = Uuid, Path, description = "Directive ID"),
+ ("key" = String, Path, description = "Memory key"),
+ ),
+ responses(
+ (status = 200, description = "Memory entry", body = DirectiveMemory),
+ (status = 404, description = "Not found", body = ApiError),
+ (status = 503, description = "Database not configured", body = ApiError),
+ ),
+ security(("bearer_auth" = []), ("api_key" = [])),
+ tag = "Directives"
+)]
+pub async fn get_memory(
+ State(state): State<SharedState>,
+ Authenticated(auth): Authenticated,
+ Path((id, key)): Path<(Uuid, String)>,
+) -> impl IntoResponse {
+ let Some(ref pool) = state.db_pool else {
+ return (
+ StatusCode::SERVICE_UNAVAILABLE,
+ Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
+ )
+ .into_response();
+ };
+
+ // Verify directive ownership
+ match repository::get_directive_for_owner(pool, auth.owner_id, id).await {
+ Ok(Some(_)) => {}
+ Ok(None) => {
+ return (
+ StatusCode::NOT_FOUND,
+ Json(ApiError::new("NOT_FOUND", "Directive not found")),
+ )
+ .into_response();
+ }
+ Err(e) => {
+ return (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Json(ApiError::new("GET_FAILED", &e.to_string())),
+ )
+ .into_response();
+ }
+ }
+
+ match repository::get_directive_memory(pool, id, &key).await {
+ Ok(Some(memory)) => Json(memory).into_response(),
+ Ok(None) => (
+ StatusCode::NOT_FOUND,
+ Json(ApiError::new("NOT_FOUND", "Memory entry not found")),
+ )
+ .into_response(),
+ Err(e) => {
+ tracing::error!("Failed to get memory: {}", e);
+ (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Json(ApiError::new("GET_FAILED", &e.to_string())),
+ )
+ .into_response()
+ }
+ }
+}
+
+/// Set (upsert) a single memory entry.
+#[utoipa::path(
+ post,
+ path = "/api/v1/directives/{id}/memories",
+ params(("id" = Uuid, Path, description = "Directive ID")),
+ request_body = SetDirectiveMemoryRequest,
+ responses(
+ (status = 200, description = "Memory entry set", body = DirectiveMemory),
+ (status = 404, description = "Directive not found", body = ApiError),
+ (status = 503, description = "Database not configured", body = ApiError),
+ ),
+ security(("bearer_auth" = []), ("api_key" = [])),
+ tag = "Directives"
+)]
+pub async fn set_memory(
+ State(state): State<SharedState>,
+ Authenticated(auth): Authenticated,
+ Path(id): Path<Uuid>,
+ Json(req): Json<SetDirectiveMemoryRequest>,
+) -> impl IntoResponse {
+ let Some(ref pool) = state.db_pool else {
+ return (
+ StatusCode::SERVICE_UNAVAILABLE,
+ Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
+ )
+ .into_response();
+ };
+
+ // Verify directive ownership
+ match repository::get_directive_for_owner(pool, auth.owner_id, id).await {
+ Ok(Some(_)) => {}
+ Ok(None) => {
+ return (
+ StatusCode::NOT_FOUND,
+ Json(ApiError::new("NOT_FOUND", "Directive not found")),
+ )
+ .into_response();
+ }
+ Err(e) => {
+ return (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Json(ApiError::new("GET_FAILED", &e.to_string())),
+ )
+ .into_response();
+ }
+ }
+
+ match repository::set_directive_memory(pool, id, &req).await {
+ Ok(memory) => Json(memory).into_response(),
+ Err(e) => {
+ tracing::error!("Failed to set memory: {}", e);
+ (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Json(ApiError::new("SET_FAILED", &e.to_string())),
+ )
+ .into_response()
+ }
+ }
+}
+
+/// Batch set multiple memory entries.
+#[utoipa::path(
+ post,
+ path = "/api/v1/directives/{id}/memories/batch",
+ params(("id" = Uuid, Path, description = "Directive ID")),
+ request_body = BatchSetDirectiveMemoryRequest,
+ responses(
+ (status = 200, description = "Memory entries set", body = Vec<DirectiveMemory>),
+ (status = 404, description = "Directive not found", body = ApiError),
+ (status = 503, description = "Database not configured", body = ApiError),
+ ),
+ security(("bearer_auth" = []), ("api_key" = [])),
+ tag = "Directives"
+)]
+pub async fn batch_set_memories(
+ State(state): State<SharedState>,
+ Authenticated(auth): Authenticated,
+ Path(id): Path<Uuid>,
+ Json(req): Json<BatchSetDirectiveMemoryRequest>,
+) -> impl IntoResponse {
+ let Some(ref pool) = state.db_pool else {
+ return (
+ StatusCode::SERVICE_UNAVAILABLE,
+ Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
+ )
+ .into_response();
+ };
+
+ // Verify directive ownership
+ match repository::get_directive_for_owner(pool, auth.owner_id, id).await {
+ Ok(Some(_)) => {}
+ Ok(None) => {
+ return (
+ StatusCode::NOT_FOUND,
+ Json(ApiError::new("NOT_FOUND", "Directive not found")),
+ )
+ .into_response();
+ }
+ Err(e) => {
+ return (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Json(ApiError::new("GET_FAILED", &e.to_string())),
+ )
+ .into_response();
+ }
+ }
+
+ match repository::batch_set_directive_memories(pool, id, &req.memories).await {
+ Ok(memories) => Json(memories).into_response(),
+ Err(e) => {
+ tracing::error!("Failed to batch set memories: {}", e);
+ (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Json(ApiError::new("SET_FAILED", &e.to_string())),
+ )
+ .into_response()
+ }
+ }
+}
+
+/// Delete a single memory entry by key.
+#[utoipa::path(
+ delete,
+ path = "/api/v1/directives/{id}/memories/{key}",
+ params(
+ ("id" = Uuid, Path, description = "Directive ID"),
+ ("key" = String, Path, description = "Memory key"),
+ ),
+ responses(
+ (status = 204, description = "Deleted"),
+ (status = 404, description = "Not found", body = ApiError),
+ (status = 503, description = "Database not configured", body = ApiError),
+ ),
+ security(("bearer_auth" = []), ("api_key" = [])),
+ tag = "Directives"
+)]
+pub async fn delete_memory(
+ State(state): State<SharedState>,
+ Authenticated(auth): Authenticated,
+ Path((id, key)): Path<(Uuid, String)>,
+) -> impl IntoResponse {
+ let Some(ref pool) = state.db_pool else {
+ return (
+ StatusCode::SERVICE_UNAVAILABLE,
+ Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
+ )
+ .into_response();
+ };
+
+ // Verify directive ownership
+ match repository::get_directive_for_owner(pool, auth.owner_id, id).await {
+ Ok(Some(_)) => {}
+ Ok(None) => {
+ return (
+ StatusCode::NOT_FOUND,
+ Json(ApiError::new("NOT_FOUND", "Directive not found")),
+ )
+ .into_response();
+ }
+ Err(e) => {
+ return (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Json(ApiError::new("GET_FAILED", &e.to_string())),
+ )
+ .into_response();
+ }
+ }
+
+ match repository::delete_directive_memory(pool, id, &key).await {
+ Ok(true) => StatusCode::NO_CONTENT.into_response(),
+ Ok(false) => (
+ StatusCode::NOT_FOUND,
+ Json(ApiError::new("NOT_FOUND", "Memory entry not found")),
+ )
+ .into_response(),
+ Err(e) => {
+ tracing::error!("Failed to delete memory: {}", e);
+ (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Json(ApiError::new("DELETE_FAILED", &e.to_string())),
+ )
+ .into_response()
+ }
+ }
+}
+
+/// Clear all memories for a directive.
+#[utoipa::path(
+ delete,
+ path = "/api/v1/directives/{id}/memories",
+ params(("id" = Uuid, Path, description = "Directive ID")),
+ responses(
+ (status = 204, description = "All memories cleared"),
+ (status = 404, description = "Directive not found", body = ApiError),
+ (status = 503, description = "Database not configured", body = ApiError),
+ ),
+ security(("bearer_auth" = []), ("api_key" = [])),
+ tag = "Directives"
+)]
+pub async fn clear_memories(
+ State(state): State<SharedState>,
+ Authenticated(auth): Authenticated,
+ Path(id): Path<Uuid>,
+) -> impl IntoResponse {
+ let Some(ref pool) = state.db_pool else {
+ return (
+ StatusCode::SERVICE_UNAVAILABLE,
+ Json(ApiError::new("DB_UNAVAILABLE", "Database not configured")),
+ )
+ .into_response();
+ };
+
+ // Verify directive ownership
+ match repository::get_directive_for_owner(pool, auth.owner_id, id).await {
+ Ok(Some(_)) => {}
+ Ok(None) => {
+ return (
+ StatusCode::NOT_FOUND,
+ Json(ApiError::new("NOT_FOUND", "Directive not found")),
+ )
+ .into_response();
+ }
+ Err(e) => {
+ return (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Json(ApiError::new("GET_FAILED", &e.to_string())),
+ )
+ .into_response();
+ }
+ }
+
+ match repository::clear_directive_memories(pool, id).await {
+ Ok(_) => StatusCode::NO_CONTENT.into_response(),
+ Err(e) => {
+ tracing::error!("Failed to clear memories: {}", e);
+ (
+ StatusCode::INTERNAL_SERVER_ERROR,
+ Json(ApiError::new("CLEAR_FAILED", &e.to_string())),
+ )
+ .into_response()
+ }
+ }
+}
diff --git a/makima/src/server/mod.rs b/makima/src/server/mod.rs
index 4cb4296..b380508 100644
--- a/makima/src/server/mod.rs
+++ b/makima/src/server/mod.rs
@@ -237,6 +237,10 @@ pub fn make_router(state: SharedState) -> Router {
.route("/directives/{id}/steps/{step_id}/fail", post(directives::fail_step))
.route("/directives/{id}/steps/{step_id}/skip", post(directives::skip_step))
.route("/directives/{id}/goal", put(directives::update_goal))
+ // Directive memory endpoints
+ .route("/directives/{id}/memories", get(directives::list_memories).post(directives::set_memory).delete(directives::clear_memories))
+ .route("/directives/{id}/memories/batch", post(directives::batch_set_memories))
+ .route("/directives/{id}/memories/{key}", get(directives::get_memory).delete(directives::delete_memory))
// Timeline endpoint (unified history for user)
.route("/timeline", get(history::get_timeline))
// Contract type templates (built-in only)
diff --git a/makima/src/server/openapi.rs b/makima/src/server/openapi.rs
index ddc2db5..f049759 100644
--- a/makima/src/server/openapi.rs
+++ b/makima/src/server/openapi.rs
@@ -3,21 +3,21 @@
use utoipa::OpenApi;
use crate::db::models::{
- AddLocalRepositoryRequest, AddRemoteRepositoryRequest, BranchInfo, BranchListResponse,
- BranchTaskRequest, BranchTaskResponse,
+ AddLocalRepositoryRequest, AddRemoteRepositoryRequest, BatchSetDirectiveMemoryRequest,
+ BranchInfo, BranchListResponse, BranchTaskRequest, BranchTaskResponse,
ChangePhaseRequest,
Contract, ContractChatHistoryResponse, ContractChatMessageRecord, ContractEvent,
ContractListResponse, ContractRepository, ContractSummary, ContractWithRelations,
CreateContractRequest, CreateDirectiveRequest, CreateDirectiveStepRequest, CreateFileRequest,
CreateManagedRepositoryRequest, CreateTaskRequest, Daemon, DaemonDirectoriesResponse,
- DaemonDirectory, DaemonListResponse, Directive, DirectiveListResponse, DirectiveStep,
- DirectiveSummary, DirectiveWithSteps,
+ DaemonDirectory, DaemonListResponse, Directive, DirectiveListResponse, DirectiveMemory,
+ DirectiveMemoryListResponse, DirectiveStep, DirectiveSummary, DirectiveWithSteps,
File, FileListResponse, FileSummary,
MergeCommitRequest, MergeCompleteCheckResponse, MergeResolveRequest, MergeResultResponse,
MergeSkipRequest, MergeStartRequest, MergeStatusResponse, MeshChatConversation,
MeshChatHistoryResponse, MeshChatMessageRecord, RepositoryHistoryEntry,
RepositoryHistoryListResponse, RepositorySuggestionsQuery, SendMessageRequest,
- Task,
+ SetDirectiveMemoryRequest, Task,
TaskEventListResponse, TaskListResponse, TaskSummary, TaskWithSubtasks, TranscriptEntry,
UpdateContractRequest, UpdateDirectiveRequest, UpdateDirectiveStepRequest,
UpdateFileRequest, UpdateGoalRequest, UpdateTaskRequest,
@@ -123,6 +123,13 @@ use crate::server::messages::{ApiError, AudioEncoding, StartMessage, StopMessage
directives::fail_step,
directives::skip_step,
directives::update_goal,
+ // Directive memory endpoints
+ directives::list_memories,
+ directives::get_memory,
+ directives::set_memory,
+ directives::batch_set_memories,
+ directives::delete_memory,
+ directives::clear_memories,
// Repository history/settings endpoints
repository_history::list_repository_history,
repository_history::get_repository_suggestions,
@@ -219,6 +226,10 @@ use crate::server::messages::{ApiError, AudioEncoding, StartMessage, StopMessage
UpdateGoalRequest,
CreateDirectiveStepRequest,
UpdateDirectiveStepRequest,
+ DirectiveMemory,
+ DirectiveMemoryListResponse,
+ SetDirectiveMemoryRequest,
+ BatchSetDirectiveMemoryRequest,
// Repository history schemas
RepositoryHistoryEntry,
RepositoryHistoryListResponse,