Skip to content

feat(memory): persistent agent memory system (Phase 1) — foundation#245

Merged
haofeif merged 12 commits into
mainfrom
feat/memory-phase1-foundation
May 22, 2026
Merged

feat(memory): persistent agent memory system (Phase 1) — foundation#245
haofeif merged 12 commits into
mainfrom
feat/memory-phase1-foundation

Conversation

@fanhongy

@fanhongy fanhongy commented May 19, 2026

Copy link
Copy Markdown
Contributor

First of 8 PRs splitting #179 (which exceeded GitHub's reviewable size — Copilot literally returned "exceeds the maximum number of lines (20,000)") and reviewer bandwidth. This PR ships Phase 1 only — file-based wiki storage, MCP store/recall/forget, CLI commands, and tiered retention.

Phases 2 onwards (SQLite metadata, BM25 search, hardening, plugin-based auto-save, LLM compilation, lint/heal, import/export, federation) follow in subsequent PRs.

What's in this PR

  • File-based wiki storage at `~/.aws/cli-agent-orchestrator/memory/{project_hash}/wiki/`
  • 4 scopes: `global`, `project` (cwd-hashed), `session`, `agent`
  • MCP tools: `memory_store`, `memory_recall`, `memory_forget`
  • CLI commands: `cao memory list/show/delete/clear`
  • Auto-inject `` context block on first user message
  • Tiered retention with background cleanup (`cleanup_service`)
  • `fcntl.flock()` for concurrent-write safety
  • 65 new tests, 100% pass

What's explicitly NOT in this PR

Resolves from #179

  • 14 CodeQL findings on `hooks/registration.py` (#3097824340–#3097972567) — resolved by deletion. The Phase 1 hook-registration code was removed from this PR; auto-save is delegated to plugins in a subsequent PR. The CodeQL warnings vaporise with the file.
  • @patricka3125 inline #3098337061 ("recommend Claude plugin instead of in-line settings.local.json") — Phase 1 no longer writes to `settings.local.json` at all. Plugin migration (separate PR) implements Pat's recommendation directly.
  • @patricka3125 inline #3098438410 ("de-scope or defer auto-save hook") — explicitly deferred from this PR. Auto-save returns via plugins.
  • @patricka3125 issue-comment #4266032266 (LLM memory-usage guidance) — partially addressed via agent-profile blocks in `developer.md`/`reviewer.md`/`code_supervisor.md`. Sub-points (scope rubric, exclusion list) will tighten in subsequent PRs.
  • @haofeif Phase 1 deliverable matrix — items shipped: `Memory` model, `MemoryService` (store/recall/forget), MCP tools, scope precedence, retention cleanup, wiki layout, `index.md`. Items deferred: SQLite metadata, REST CRUD, per-scope cap → addressed in subsequent PRs.

Test plan

  • `uv run pytest test/services/test_memory_service.py test/cli/commands/test_memory.py test/providers/test_memory_injection.py` → 65/65 pass
  • `uv run black --check src/ test/` → clean
  • `uv run isort --check-only src/ test/` → clean
  • CI on this branch passes
  • Manual smoke: `cao memory list` against a fresh `~/.aws/cli-agent-orchestrator/memory/` dir

Note for dogfood

Until the plugin migration PR lands, agents will not auto-save. They must call `memory_store` explicitly. The agent-profile guidance blocks instruct them to do so when storing facts that should outlive the session.

🤖 Generated with Claude Code

fanhongy and others added 2 commits May 19, 2026 16:19
  - File-based wiki storage under ~/.aws/cli-agent-orchestrator/memory/
  - 4 scopes: global, project (cwd-hashed), session, agent
  - MCP tools: memory_store, memory_recall, memory_forget
  - CLI commands: cao memory list/show/delete/clear
  - Auto-inject <cao-memory> context block on first message
  - Auto-save hooks: Claude Code (Stop + PreCompact), Kiro CLI (agentSpawn + userPromptSubmit)
  - Tiered retention with background cleanup
  - fcntl.flock() for concurrent-write safety
  - 1296 tests passing
CI black --check flagged 5 files. Run black to align with the
formatter version pinned in CI.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Comment thread src/cli_agent_orchestrator/hooks/registration.py Fixed
Phase 1 ships memory primitives (store/recall/forget) only. Hook-driven
auto-save is delivered via per-provider plugins in a subsequent PR
(per @patricka3125's recommendation in #179 to use Claude Code's plugin
system rather than in-line settings.local.json edits).

Removes:
- src/cli_agent_orchestrator/hooks/registration.py
- src/cli_agent_orchestrator/hooks/*.sh
- terminal_service hook registration call site
- api/main.py install_hooks() call
- cleanup_service stale-hook-flag sweep
- docs/memory.md auto-save section

Side effect: 14 CodeQL findings on hooks/registration.py vaporise with
the file. Resolves the path-injection warnings flagged on PR #179.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@haofeif haofeif requested a review from Copilot May 19, 2026 06:51
@haofeif haofeif added the enhancement New feature or request label May 19, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces Phase 1 of a persistent, file-backed “memory” subsystem for CLI Agent Orchestrator, including a new MemoryService, MCP tools (memory_store/recall/forget), CLI commands (cao memory ...), and first-message context injection (<cao-memory>), plus retention cleanup.

Changes:

  • Added file-based wiki storage + indexing for scoped memories (global/project/session/agent) via MemoryService.
  • Exposed memory operations through MCP tools and added CLI commands to inspect/manage stored memories.
  • Added background cleanup for retention and extensive unit/integration test coverage.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/cli_agent_orchestrator/services/memory_service.py Core file-based storage/recall/forget + context block generation.
src/cli_agent_orchestrator/services/terminal_service.py Injects <cao-memory> into first message (provider-dependent) and cleans up injection state on terminal deletion.
src/cli_agent_orchestrator/services/cleanup_service.py Adds memory retention cleanup pass.
src/cli_agent_orchestrator/models/memory.py Introduces Memory, MemoryScope, MemoryType model/enums.
src/cli_agent_orchestrator/mcp_server/server.py Adds MCP tools: memory_store, memory_recall, memory_forget.
src/cli_agent_orchestrator/cli/commands/memory.py Adds cao memory list/show/delete/clear commands.
src/cli_agent_orchestrator/cli/main.py Registers the new memory CLI command group.
src/cli_agent_orchestrator/api/main.py Runs memory cleanup on startup; adds /terminals/{id}/memory-context endpoint.
src/cli_agent_orchestrator/constants.py Adds MEMORY_BASE_DIR constant under CAO home directory.
docs/memory.md Documents scopes, types, tools, injection behavior, storage layout, retention.
README.md Adds memory system feature to top-level project feature list.
CHANGELOG.md Adds Unreleased entry describing Phase 1 memory functionality.
src/cli_agent_orchestrator/agent_store/developer.md Adds guidance to use memory tools.
src/cli_agent_orchestrator/agent_store/reviewer.md Adds guidance to use memory tools.
src/cli_agent_orchestrator/agent_store/code_supervisor.md Adds guidance to use memory tools.
test/services/test_memory_service.py Extensive store/recall/forget/cleanup/security tests for MemoryService.
test/providers/test_memory_injection.py Tests first-message injection behavior + “no config file mutation” guarantees.
test/cli/commands/test_memory.py Tests CLI command behaviors with mocked MemoryService.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/cli_agent_orchestrator/services/memory_service.py
Comment thread src/cli_agent_orchestrator/services/memory_service.py
Comment thread src/cli_agent_orchestrator/services/memory_service.py
Comment thread src/cli_agent_orchestrator/services/memory_service.py Outdated
Comment thread src/cli_agent_orchestrator/services/memory_service.py Outdated
Comment thread src/cli_agent_orchestrator/mcp_server/server.py
Comment thread src/cli_agent_orchestrator/mcp_server/server.py
Comment thread src/cli_agent_orchestrator/cli/commands/memory.py
Comment thread src/cli_agent_orchestrator/api/main.py
Comment thread src/cli_agent_orchestrator/services/terminal_service.py Outdated
@haofeif

haofeif commented May 19, 2026

Copy link
Copy Markdown
Contributor

PR Review — Memory System Phase 1

Combined findings — Copilot + manual review (verified)

All findings re-verified against PR head (f05ee6b). The "Copilot" column shows whether the finding came from the automated reviewer; items marked "—" are additional from manual review.

P0 — block merge; ships the feature broken

ID Copilot Issue File:Line
P0-A yes Project cwd always None → project scope routes to global dir memory_service.py:652
P0-B yes Kiro (default provider) skipped from memory injection with no replacement hook terminal_service.py:353

Why P0:

  • P0-A_get_terminal_context() returns {"cwd": None, ...} hardcoded (line 651-652). The comment claims "resolved dynamically via tmux"; no such resolution is wired. With cwd=None, resolve_scope_id("project", ctx) returns None, then _get_project_dir (line 117-122) falls back to base_dir / "global". Tests pass only because they monkeypatch _get_terminal_context. Production silently broken.
  • P0-Bterminal_service.py:353 skips inject_memory_context when provider == kiro_cli, comment says "Kiro uses the AgentSpawn hook for injection." But commit f05ee6b deleted the entire hooks/ directory, including cao_kiro_prompt_hook.sh. Kiro is the default provider; on main after this lands, most users get zero memory injection.

P1 — block merge; user-visible correctness bugs

ID Copilot Issue File:Line
P1-A yes scope_id computed but ignored on disk for session/agent scopes memory_service.py:123
P1-B yes Retention keyed on memory_type, not scope cleanup_service.py:197
P1-C yes scope="project" + missing cwd silently falls back to global directory memory_service.py:177

Why P1:

  • P1-Aresolve_scope_id (lines 53-58) computes session_name / agent_profile as scope_id, but _get_project_dir (lines 117-122) ignores them and returns base_dir / "global". Different sessions and different agents collide on the same global/wiki/{session,agent}/<key>.md path. Cross-leakage on key collision; isolation API contract is misleading.
  • P1-Bcleanup_service.py:73-79 keys retention dict on memory_type (user/feedback/project/reference). docs/memory.md claims scope-based retention (global/agent never expire, project 90d, session 14d). A global-scoped memory with memory_type="project" expires at 90d, contradicting the doc.
  • P1-C — Same code path as P0-A. Even after P0-A is fixed, this remains the secondary safety net: when caller passes scope="project" without resolvable cwd, raise rather than silently mixing into global.

P2 — fix in this PR; cheap correctness

ID Copilot Issue File:Line
P2-A yes tags: regex truncates hyphenated tags (ci-cdci) memory_service.py:486
P2-B yes forget() blanks the index <!-- Updated: --> header memory_service.py:561
P2-C yes Bad ISO8601: isoformat() + "Z" produces +00:00Z mcp_server/server.py:868
P2-D yes "action" inferred via timestamp comparison; can mis-report on same-second upsert mcp_server/server.py:813
P2-E yes CLI accepts longer keys than service truncates (60-char limit) cli/commands/memory.py:36
P2-F yes Synchronous cleanup task on event loop at startup api/main.py:166

Verification notes:

  • P2-A — regex literal at line 486 is r"tags: ([^-\n|]+?)(?:\s*-->|\s*\|)". The negated class excludes -. Confirmed.
  • P2-Bforget() at line 561 calls _update_index(..., "", "", "", "", "remove"). _update_index at line 290 unconditionally rewrites <!-- Updated: {timestamp} -->. Empty timestamp produces <!-- Updated: -->.
  • P2-C — Memory's updated_at is parsed with replace(tzinfo=timezone.utc), so it is tz-aware. datetime.isoformat() on a tz-aware UTC time produces 2026-05-19T06:00:00+00:00. Concatenating "Z" yields invalid 2026-05-19T06:00:00+00:00Z.
  • P2-D — Storage timestamp format is %Y-%m-%dT%H:%M:%SZ (second granularity). Recommended fix: have MemoryService.store() (which already computes action = "updated" if is_update else "created" at line 213) return the action so the MCP layer doesn't re-infer.
  • P2-E_VALID_KEY_RE is r"^[a-z0-9\-]+$" — character set only. _sanitize_key truncates at 60 (line 105). Add length check at the CLI boundary too.
  • P2-Fcleanup_expired_memories at cleanup_service.py:82 is async def but the body uses sync Path.glob, read_text, regex. The companion call cleanup_old_data two lines above uses asyncio.to_thread — match that pattern.

P3 — defer; nits and process

ID Copilot Issue Note
P3-A Stale GHAS CodeQL bot comment on deleted hooks/registration.py Commit f05ee6b removed the file; bot finding stale. Dismiss in UI; no code change.
P3-B No production-path test for _get_terminal_context() Once P0-A is fixed, add a test that exercises real tmux lookup so it can't silently regress.
P3-C Verify docs/memory.md reflects actual retention policy after P1-B fix Doc-only; required when resolving P1-B.

(Earlier draft had a separate "file roundtrip in store()" finding; on re-verification this is the same fix as P2-D — store() already knows action and should return it. Merged into P2-D. Not a separate finding.)

Tally

Source P0 P1 P2 P3 Total
Copilot 2 3 6 0 11
Manual 0 0 0 3 3
Combined 2 3 6 3 14

Merge gate summary

  • 5 issues block merge (2× P0, 3× P1) — all from Copilot.
  • 6 issues should land in this PR (P2 — all cheap, all from Copilot).
  • 3 are deferable (P3 — all from manual review; primarily test/doc/process).

Copilot's review accuracy on this PR was 100% — every cited line was verified against actual code at PR head f05ee6b. The manual review contribution is severity framing, test coverage gap, doc consistency, and the GHAS bot stale-comment cleanup — not safety-critical, but useful.

Process notes (not findings)

  • The 8-PR split structure is sensible. Approve the split independent of code review; remaining 7 PRs will be reviewable in isolation.

Suggested fix order

  1. Wire cwd into _get_terminal_context() from tmux (P0-A)
  2. Re-enable memory injection for Kiro until the plugin PR lands, or explicitly scope this PR to non-Kiro providers in the description (P0-B)
  3. Decide: nest scope_id in the path, or strip it from the API (P1-A, P1-C)
  4. Key retention on scope, not type; update docs/memory.md to match (P1-B, P3-C)
  5. Sweep P2: tags regex / forget timestamp / ISO8601 / store() returns action / CLI key length / to_thread cleanup task
  6. Add the missing _get_terminal_context smoke test (P3-B)
  7. Dismiss the stale GHAS comment in GitHub UI (P3-A)

Net ask: ~10–15 lines of substantive code change to clear P0+P1, ~30–40 lines to also clear P2, plus one doc update. Reasonable in a single revision.

P0 — silent breakage:
- _get_terminal_context() now resolves cwd via the existing tmux
  working-directory helper, so project-scope storage works in
  production rather than only in tests that monkeypatch the helper.
- send_input() injects memory context for every provider, including
  Kiro (the default). The original Phase 1 commit skipped Kiro
  expecting the AgentSpawn hook to handle it; that hook lands in a
  later plugin-migration PR, so inline injection is the correct
  Phase 1 path for now.

P1 — correctness gaps:
- Session and agent scopes nest scope_id into wiki paths, so two
  sessions or two agent profiles can no longer collide on the same
  key. Index entries follow the same nested shape.
- Retention is keyed on scope (global/agent never expire; project 90d;
  session 14d) rather than memory_type, with user/feedback as a
  scope-independent override. Matches docs/memory.md.
- store(scope="project") raises when no working directory is
  resolvable, instead of silently mixing project memories into the
  global container.

P2 — cheap correctness:
- tags: regex no longer truncates hyphens, so tags like ci-cd parse
  intact.
- forget() passes the current timestamp into the index header so
  removals don't blank the <!-- Updated: --> line.
- updated_at serialises via strftime("%Y-%m-%dT%H:%M:%SZ"), avoiding
  the invalid "+00:00Z" form for tz-aware datetimes.
- store() returns the action ("created" or "updated") on the Memory
  model so the MCP layer no longer infers it from second-granularity
  timestamps.
- CLI key validation enforces the same 60-char ceiling as
  MemoryService._sanitize_key.
- cleanup_expired_memories() offloads sync glob/parse work to
  asyncio.to_thread so it doesn't block the FastAPI event loop on
  startup.

P3 — process:
- Added a production-path test for _get_terminal_context() that
  doesn't monkeypatch the function itself, so a P0-A regression
  surfaces immediately.
- docs/memory.md now matches the scope-keyed retention semantics.

67 tests pass; black and isort clean.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@fanhongy

fanhongy commented May 19, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the input @haofeif. All 12 findings addressed in 5af8217 except P3-A (stale CodeQL bot comment, dismissed in UI). Per-row resolution:

ID Resolution
P0-A _get_terminal_context() now resolves cwd via terminal_service.get_working_directory() (lazy import to break the cycle). Production path no longer silently routes project-scoped stores into the global container.
P0-B send_input() injects memory context for every provider, including Kiro. The skip was predicated on the AgentSpawn hook handling injection; that hook ships in the plugin-migration PR (#4 in the 8-PR plan), so inline is the right Phase 1 path.
P1-A get_wiki_path() and _update_index() now nest scope_id for session and agent scopes (session/<scope_id>/<key>.md). No more cross-session/cross-agent collisions on identical keys.
P1-B Retention is now scope-keyed: SCOPE_RETENTION_DAYS = {global: None, agent: None, project: 90, session: 14}. user/feedback memory_types are a scope-independent override that never expires. Matches docs/memory.md.
P1-C store(scope="project") now raises ValueError when scope_id cannot be resolved, rather than silently demoting to global/.
P2-A tags: regex changed from `[^-\n
P2-B forget() passes the current timestamp into _update_index, so deletes update the <!-- Updated: --> header rather than blanking it.
P2-C updated_at serializes via strftime("%Y-%m-%dT%H:%M:%SZ") — no more +00:00Z malformed strings for tz-aware datetimes.
P2-D Memory model gained an action: Optional[str] field (excluded from on-disk serialization). store() sets it; the MCP layer reads it directly instead of inferring from same-second timestamp comparison.
P2-E _validate_key() in cli/commands/memory.py enforces the 60-char ceiling that MemoryService._sanitize_key() already silently truncates to. CLI no longer accepts keys it can never look up.
P2-F cleanup_expired_memories() wraps the sync Path.glob and _find_expired_entries in asyncio.to_thread, matching the pattern in the sibling cleanup_old_data task. No more blocking the event loop on startup with a populated memory dir.
P3-A Stale GHAS CodeQL comment on the deleted hooks/registration.py — will dismiss in the UI. The file vaporised in f05ee6b.
P3-B New TestTerminalContextResolution class in test_memory_service.py exercises _get_terminal_context() end-to-end with database + tmux helpers stubbed (NOT the function itself), so a P0-A regression surfaces immediately.
P3-C docs/memory.md updated to describe scope-keyed retention with the user/feedback override.

Test plan: 67 tests pass locally (65 + 2 new for P3-B). black --check and isort --check-only clean. Will re-verify against CI.

Implementation notes worth flagging:

  • For P1-A I went with the "actually fix isolation" option (nest scope_id in path) rather than the "strip the misleading return" option. Slightly more code (~15 lines) but it preserves the API contract that session/agent are isolated tiers.
  • For P2-D I extended the Memory Pydantic model with an action field rather than changing store()'s return type to a tuple. Less invasive for the existing test fixtures and other callers.

Ready for re-review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 6 comments.

Comment thread src/cli_agent_orchestrator/services/memory_service.py Outdated
Comment thread src/cli_agent_orchestrator/services/memory_service.py
Comment thread src/cli_agent_orchestrator/services/memory_service.py Outdated
Comment thread src/cli_agent_orchestrator/services/cleanup_service.py
Comment thread src/cli_agent_orchestrator/mcp_server/server.py
Comment thread docs/memory.md Outdated
fanhongy and others added 2 commits May 20, 2026 14:14
Six findings from the second Copilot pass: rewrite wiki header on
upsert so recall() sees fresh type/tags; normalize tag whitespace
so the index regex still matches; offload forget() in cleanup via
asyncio.to_thread; add success:True to memory_recall envelope;
fix misleading container comment and storage-layout doc diagram.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 5 comments.

Comment thread src/cli_agent_orchestrator/services/terminal_service.py
Comment thread src/cli_agent_orchestrator/services/memory_service.py
Comment thread src/cli_agent_orchestrator/services/memory_service.py Outdated
Comment thread src/cli_agent_orchestrator/services/memory_service.py Outdated
Comment thread src/cli_agent_orchestrator/cli/commands/memory.py Outdated
fanhongy and others added 2 commits May 21, 2026 13:46
fix as suggested

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 5 comments.

Comment thread src/cli_agent_orchestrator/services/memory_service.py Outdated
Comment thread src/cli_agent_orchestrator/services/memory_service.py
Comment thread src/cli_agent_orchestrator/services/memory_service.py
Comment on lines +645 to +657
async def store_one(i: int):
return await svc.store(
content=f"Concurrent memory entry {i}",
scope="global",
memory_type="project",
key=f"concurrent-{i}",
terminal_context=ctx,
)

async def run_concurrent():
return await asyncio.gather(*[store_one(i) for i in range(5)])

results = asyncio.run(run_concurrent())
Comment on lines +742 to +756
response = requests.get(f"{API_BASE_URL}/terminals/{terminal_id}")
response.raise_for_status()
meta = response.json()
ctx: Dict[str, Any] = {
"terminal_id": meta["id"],
"session_name": meta["session_name"],
"provider": meta["provider"],
"agent_profile": meta.get("agent_profile"),
}
# Try to get working directory for project scope resolution
try:
wd_resp = requests.get(f"{API_BASE_URL}/terminals/{terminal_id}/working-directory")
if wd_resp.status_code == 200:
ctx["cwd"] = wd_resp.json().get("working_directory")
except Exception:
fanhongy and others added 3 commits May 21, 2026 14:12
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@fanhongy

Copy link
Copy Markdown
Contributor Author

One last High issue fixed in last commit, should be good to merge

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 18 out of 18 changed files in this pull request and generated 5 comments.

Comment on lines +57 to +75
# Track terminals that have already received memory injection (first message only).
_memory_injected_terminals: set = set()


def inject_memory_context(first_message: str, terminal_id: str) -> str:
"""Prepend <cao-memory> context block to the first user message.

Tracks which terminals have already been injected so that only the very
first user message after init receives the memory block.

Calls MemoryService.get_memory_context_for_terminal() which returns
a formatted <cao-memory>...</cao-memory> block (or empty string if
no memories exist). Stateless — no file mutation, no backup/restore.
"""
if terminal_id in _memory_injected_terminals:
return first_message

_memory_injected_terminals.add(terminal_id)

Comment on lines +518 to +533
@app.get("/terminals/{terminal_id}/memory-context")
async def get_terminal_memory_context(terminal_id: TerminalId):
"""Return the CAO memory context block for a terminal as plain text.

Used by the Kiro AgentSpawn hook to inject memory into agent context.
Returns empty 200 if no memories exist for this terminal.
"""
from fastapi.responses import PlainTextResponse

try:
from cli_agent_orchestrator.services.memory_service import MemoryService

svc = MemoryService()
context = svc.get_memory_context_for_terminal(terminal_id)
return PlainTextResponse(content=context)
except ValueError as e:
Comment on lines +159 to +172
async def store(
self,
content: str,
scope: str = "project",
memory_type: str = "project",
key: Optional[str] = None,
tags: str = "",
terminal_context: Optional[dict] = None,
) -> Memory:
"""Store or update a memory. Upserts wiki file + index.md.

Declared async for compatibility with async callers (MCP server, FastAPI).
File I/O is synchronous; a future improvement would use aiofiles.
"""
Comment on lines +480 to +492
# Sort by updated_at descending
results.sort(key=lambda m: m.updated_at, reverse=True)

# Apply scope precedence ordering when no scope filter
if not scope:
precedence = {
MemoryScope.SESSION.value: 0,
MemoryScope.PROJECT.value: 1,
MemoryScope.GLOBAL.value: 2,
MemoryScope.AGENT.value: 3,
}
results.sort(key=lambda m: (precedence.get(m.scope, 99), -m.updated_at.timestamp()))

Comment on lines +735 to +758
def _get_terminal_context_from_env() -> Optional[Dict[str, Any]]:
"""Build terminal context dict from the calling terminal's CAO_TERMINAL_ID."""
terminal_id = os.environ.get("CAO_TERMINAL_ID")
if not terminal_id:
return None

try:
response = requests.get(f"{API_BASE_URL}/terminals/{terminal_id}")
response.raise_for_status()
meta = response.json()
ctx: Dict[str, Any] = {
"terminal_id": meta["id"],
"session_name": meta["session_name"],
"provider": meta["provider"],
"agent_profile": meta.get("agent_profile"),
}
# Try to get working directory for project scope resolution
try:
wd_resp = requests.get(f"{API_BASE_URL}/terminals/{terminal_id}/working-directory")
if wd_resp.status_code == 200:
ctx["cwd"] = wd_resp.json().get("working_directory")
except Exception:
pass
return ctx

@haofeif haofeif left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fanhongy LGTM, thanks for the great one

@haofeif haofeif merged commit 93d1f63 into main May 22, 2026
16 checks passed
@haofeif haofeif deleted the feat/memory-phase1-foundation branch May 22, 2026 03:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants