Skip to content

feat: optional Dakera project memory — recall decisions across sessions#1187

Open
ferhimedamine wants to merge 2 commits into
Pythagora-io:mainfrom
ferhimedamine:feat/dakera-project-memory
Open

feat: optional Dakera project memory — recall decisions across sessions#1187
ferhimedamine wants to merge 2 commits into
Pythagora-io:mainfrom
ferhimedamine:feat/dakera-project-memory

Conversation

@ferhimedamine

Copy link
Copy Markdown

Problem

Pythagora builds applications iteratively across many separate conversations. Each
new conversation re-loads project context from the spec file and the file tree, but the
reasoning from earlier sessions is lost: architectural decisions, debugging
resolutions and conventions get re-derived from scratch every time. A concrete example
from the linked issue: "we hit
a Pydantic validation error on the user schema — resolved by adding Optional types"
is
solved once, then forgotten, and the same class of bug is re-encountered later.

Design

This PR adds an opt-in semantic memory layer backed by a self-hosted
Dakera server, wired into the two natural points in the agent loop:

  • Developer.breakdown_current_task — recalls the decisions/bug-fixes most relevant
    to the current task (POST /v1/memory/recall) and offers them to the LLM as extra
    context via a new prompt partial. Mirrors how docs are injected today.
  • TaskCompleter — on task completion, stores a compact outcome summary
    (POST /v1/memory/store), including the problem -> solution pairs from the debugging
    iterations, which are the most valuable thing to carry forward.

Memories are namespaced per project (agent_id = "gptpilot-<project_id>"), so recall
only ever surfaces knowledge from the same project.

Design choices

  • Fully opt-in, zero behaviour change by default. A new optional memory section is
    added to the config (core.config.DakeraConfig). When it is omitted (the default),
    ProjectMemory.for_project() returns None, both hooks become no-ops, and the
    injected prompt partial renders an empty string — the breakdown prompt is
    byte-identical to today.
  • No new dependency. The client is a thin async wrapper over the existing httpx
    dependency — no SDK is added to pyproject.toml / requirements.txt.
  • Best-effort, never blocks a build. Every call catches httpx.HTTPError and
    degrades to "no memory" with a warning, exactly the way
    ExternalDocumentation degrades when the docs API is unreachable. A slow or down
    memory server can never fail or stall a build.

Files

File Change
core/config/__init__.py New DakeraConfig; optional Config.memory field
core/memory/__init__.py New ProjectMemory async client (recall / store)
core/agents/developer.py Gated recall injected into task breakdown
core/agents/task_completer.py Gated store of the task outcome on completion
core/prompts/partials/dakera_memory.prompt New prompt partial (renders only when memories exist)
core/prompts/developer/breakdown.prompt Include the partial
example-config.json Documented (commented-out) memory section
docs/MEMORY.md Feature docs
tests/memory/test_dakera.py Unit tests

Usage

Run a server (compose from dakera-ai/dakera-deploy, listens on port 3000), then add to config.json:

"memory": {
  "base_url": "http://localhost:3000",
  "api_key": "dk-your-key",
  "top_k": 5
}

Testing

  • tests/memory/test_dakera.py — 12 tests covering: disabled/unconfigured → None,
    per-project namespacing, recall parsing + score ordering + skipping malformed entries,
    X-API-Key header presence/absence, request payloads for recall & store, and graceful
    degradation on network errors (recall → [], store → False).
  • Verified the prompt partial renders memories when present and an empty string when the
    list is empty or undefined (proving default behaviour is unchanged).
  • ruff check and ruff format --check clean; existing tests/config, tests/agents
    and tests/templates suites pass unchanged (47 passed, 6 pre-existing skips).

Checklist

  • Opt-in; no behaviour change when disabled
  • No new dependencies
  • Follows existing patterns (ExternalDocumentation httpx usage, prompt partials, config sub-models)
  • Unit tests added and passing
  • ruff lint + format clean
  • Docs added (docs/MEMORY.md, example-config.json)

Closes #1186.

Pythagora builds apps across many separate conversations. Each new conversation
re-loads project context from the spec and file tree, but the reasoning from
earlier sessions (architectural decisions, debugging resolutions, conventions) is
lost and re-derived from scratch every time.

This adds an opt-in semantic memory backed by a self-hosted Dakera server:

- On task breakdown, recall the decisions/bug-fixes most relevant to the task and
  offer them to the Developer agent as extra context.
- On task completion, store a short summary of the outcome, including problem ->
  solution pairs from the debugging iterations.

The feature is fully opt-in via a new optional 'memory' config section; when it is
omitted, behaviour is unchanged (the injected prompt partial renders nothing).
Every call to the memory server is best-effort and degrades to 'no memory' on any
error, mirroring how ExternalDocumentation degrades when the docs API is down. No
new dependency is added -- the client reuses the existing httpx dependency.

Adds unit tests (tests/memory/test_dakera.py) and docs/MEMORY.md.
@CLAassistant

CLAassistant commented Jul 2, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@ferhimedamine

Copy link
Copy Markdown
Author

I have read the CLA Document and I hereby sign the CLA

@hegu-1

hegu-1 commented Jul 3, 2026

Copy link
Copy Markdown

Thanks for the detailed follow-up — this is a strong implementation shape.

The parts I like most are:

  • opt-in by default, so memory does not silently change agent behavior;
  • project namespacing, which prevents cross-project contamination;
  • treating recalled memory as prior context rather than instruction;
  • storing debugging iterations as problem -> solution pairs instead of only a flat task summary.

One thing I’d still watch closely is the boundary between “candidate memory” and “durable project truth.”

Even if the recall prompt tells the agent to ignore outdated items, the memory object itself may still benefit from explicit lifecycle metadata, for example:

memory_type: decision | failed_path | bug_fix | convention | open_question
status: candidate | accepted | superseded | rejected
source_session_id
last_confirmed_at
supersedes

That would make stale-memory handling less dependent on prompt behavior and more visible to the runtime/UI later.

The current PR looks like a good v1 because it is opt-in and non-blocking. My main suggestion would be to keep the door open for memory lifecycle and supersession metadata, especially once users start relying on recalled project decisions across many sessions.

Addresses review feedback (@hegu-1 on Pythagora-io#1187): make stale-memory handling less
dependent on prompt behaviour by tagging each stored outcome with lightweight
lifecycle metadata that is visible to the runtime/UI.

- ProjectMemory.store() now accepts an optional metadata dict, persisted verbatim
  on the memory (the Dakera store API already supports it).
- TaskCompleter classifies each outcome: bug_fix when the task needed debugging
  iterations, otherwise decision, and writes metadata {kind, status: candidate,
  source}. Outcomes are stored as candidates, never as accepted project truth,
  keeping the door open for explicit supersession later without touching recall.

Adds tests for metadata passthrough (present/absent) and the bug_fix/decision
classification, and documents the lifecycle in docs/MEMORY.md.
@ferhimedamine

Copy link
Copy Markdown
Author

Thanks @hegu-1 — good call on the candidate-vs-durable boundary. I've implemented the lightweight version of your lifecycle metadata in 3a30306:

  • kind — each stored outcome is classified bug_fix (the task needed debugging iterations) or decision (it didn't). I deliberately only emit the two kinds that can be inferred honestly from task-completion data rather than guessing at convention / failed_path / open_question.
  • status: candidate — everything is written as a candidate, never as accepted project truth, which is exactly the boundary you flagged. Promotion (accepted) / superseded can be layered on later by the runtime/UI without touching the recall path.
  • source: gpt-pilot plus a bug-fix / decision tag for filterable recall.

So stale handling no longer relies on prompt behaviour alone — the lifecycle is now on the memory object itself. source_session_id / last_confirmed_at / supersedes are the natural next layer once there's a UI consuming this; I kept the metadata shape open for them.

Still opt-in, non-blocking, no new dependency; added tests for the metadata passthrough and the classification, and documented it in docs/MEMORY.md. Appreciate the review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Semantic project memory via Dakera — recall architectural decisions across sessions

3 participants