Skip to content

feat(signal): wire Signal conversations into OODA context + graph memory (#2527 follow-up)#2625

Open
rysweet wants to merge 4 commits into
mainfrom
feat/signal-ooda-context-and-graph-memory
Open

feat(signal): wire Signal conversations into OODA context + graph memory (#2527 follow-up)#2625
rysweet wants to merge 4 commits into
mainfrom
feat/signal-ooda-context-and-graph-memory

Conversation

@rysweet

@rysweet rysweet commented Jul 6, 2026

Copy link
Copy Markdown
Owner

The gap

Follow-up to the closed issue #2527, which introduced the shared ConversationChannel/MeetingBackend abstraction. Chatting with Simard over Signal did not start with the same OODA-loop context as the CLI meeting, and was not wired into the graph-based cognitive-memory model.

On main, src/signal_conversation/channel.rs run() built per-operator backends inside the make_backend closure via:

MeetingBackend::new_session("signal", agent, None, load_meeting_system_prompt())

— a bare system prompt and a None cognitive-memory bridge. So over Signal Simard had:

  • no OODA-loop context at session start (no goals/decisions/operator-identity/projects recall), and
  • no memory write-back on /closemeeting_backend::closing consolidation is a no-op while the bridge is None.

The CLI reference (operator_commands_meeting::meeting_session::run_meeting_repl_command) already does the right thing: launches a real bridge, builds live context, enriches the prompt, and passes the bridge into the backend.

The fix (DRY — mirrors the CLI)

  1. Shared helper — add pub(crate) build_enriched_meeting_system_prompt(bridge) in meeting_session.rs = base meeting_system.md prompt + build_live_meeting_context(bridge), propagating bridge errors (PHILOSOPHY.md: no silent degradation). It is now the single source of truth for "what context a Simard conversation starts with".

  2. Re-export it from operator_commands_meeting::mod.

  3. Refactor the CLI run_meeting_repl_command to use the shared helper (behavior-preserving).

  4. Wire the Signal channel — in channel.rs run(), each per-operator backend built in make_backend now:

    • (recall) opens its own cognitive-memory store via memory_ipc::launch_writer_bridge on state_root::simard_state_root() (shares the running daemon's store over IPC when one is up),
    • builds the enriched system prompt via the shared helper, and
    • (write-back) passes Some(memory) into MeetingBackend::new_session, so /close consolidates episodes / summary facts / goal carryover into graph memory.

    The make_backend closure now returns SimardResult<MeetingBackend> (and run_continuous's generic bound is widened to match) so recall/memory failures propagate rather than degrade silently. The fail-fast provider check and the UnavailableAgent graceful-degradation path are preserved. The binding is named memory (never bridge) per the signal_conversation naming guardrail, and the now-unused private load_meeting_system_prompt is removed.

Adapted to current main

Re-implemented against main's per-operator run_continuous/make_backend structure introduced by the Note-to-Self sync-sent loop guard (#2575) and continuous per-operator conversations (#2577) — not the old single-backend run(). Each per-operator backend gets its own store instance because the store is moved into the backend.

Everything stays behind the default-off signal cargo feature.

Verification

  • cargo build --features signal
  • cargo test --features signal signal_conversation ✅ (88 passed)
  • cargo test operator_commands_meeting:: ✅ (66 passed)
  • cargo clippy --features signal -- -D warnings
  • default cargo clippy -- -D warnings
  • pre-push cargo clippy --all-targets --all-features --locked -- -D warnings

Refs #2527, #2575, #2577.

rysweet and others added 4 commits July 6, 2026 00:15
…ory (#2527 follow-up)

Chatting with Simard over Signal now starts with the SAME OODA-loop context
as the CLI meeting and is bidirectionally wired into Simard's graph-based
cognitive-memory model: live OODA context is recalled into the system prompt
at session start, and conversation memory is written back on /close.

Previously src/signal_conversation/channel.rs built per-operator backends via
MeetingBackend::new_session("signal", agent, None, load_meeting_system_prompt())
— a bare prompt and a None memory bridge — so over Signal Simard had neither
OODA-loop context nor memory write-back (closing.rs consolidation is a no-op
while the bridge is None).

Changes (DRY, mirrors the CLI run_meeting_repl_command):
- operator_commands_meeting::meeting_session: add pub(crate)
  build_enriched_meeting_system_prompt(bridge) = base meeting_system.md prompt
  + build_live_meeting_context(bridge), propagating bridge errors
  (PHILOSOPHY.md: no silent degradation). Refactor the CLI REPL to use it.
- operator_commands_meeting::mod: re-export the shared helper.
- signal_conversation::channel run(): wire each per-operator backend in the
  make_backend closure — open its OWN cognitive-memory store via
  memory_ipc::launch_writer_bridge on state_root::simard_state_root() (recall),
  build the enriched system prompt via the shared helper, and pass Some(memory)
  into MeetingBackend::new_session (write-back on /close). The closure now
  returns SimardResult<MeetingBackend> so recall/memory failures propagate;
  run_continuous's bound is widened to match. The fail-fast provider check and
  the UnavailableAgent graceful-degradation path are preserved. The binding is
  named `memory` (not `bridge`) per the signal_conversation naming guardrail,
  and the now-unused private load_meeting_system_prompt is removed.

Adapted to main's per-operator run_continuous/make_backend structure introduced
by the Note-to-Self loop guard (#2575) and continuous per-operator conversations
(#2577), not the old single-backend run(). Everything stays behind the
default-off "signal" cargo feature.

Verified: cargo build --features signal; cargo test --features signal
signal_conversation (88 passed); cargo test operator_commands_meeting::
(66 passed); cargo clippy --features signal -- -D warnings; default clippy.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…close comment

Address the architect's Step 6b documentation review for the Signal OODA /
graph-memory wiring (PR #2625, issue #2527 follow-up). The feature code shipped
without the doc updates it implies, leaving three artifacts stale/incomplete.

Code-doc:
- meeting_backend/closing.rs: the "Memory consolidation" comment claimed the
  step is a no-op because the bridge is "always None". It no longer is — the
  Signal channel wires a per-operator cognitive-memory store into the backend,
  so `/close` consolidates back into graph memory. Rewrite to state exactly
  when it runs (Signal write-back) vs when it is skipped (CLI meeting REPL and
  dashboard chat pass None; they recall context into the prompt but do not
  write back here).
- signal_conversation/channel.rs: the module doc now describes the OODA /
  graph-memory wiring, and the "# Naming" section is made precisely truthful —
  no symbol *defined here* is named bridge/Bridge, but the wiring does call the
  external `memory_ipc::launch_writer_bridge`, whose handle is bound locally as
  `memory`.

Reference docs:
- signal-continuous-conversation.md: add an "Also new (#2527)" note, two
  concepts rows (OODA context, cognitive-memory store), and note that `/close`
  consolidates into graph memory. Refine the naming note for the external
  helper.
- signal-conversation.md: refine the naming note, add an OODA/graph-memory
  paragraph, and note memory consolidation in the meeting-turn data flow.

Verified: mkdocs build --strict (0 broken links/anchors), clippy --features
signal, and that build_live_meeting_context stays pub(super) (the channel
reaches recall only through build_enriched_meeting_system_prompt).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…2527 follow-up)

Add the missing regression tests for the Signal↔OODA-context / graph-memory
wiring so the new behaviour is guarded (TDD step of the #2527 follow-up,
adapted to the per-operator run_continuous/make_backend structure of
#2575/#2577):

operator_commands_meeting::tests_live_context — pin the shared
build_enriched_meeting_system_prompt helper (the single source of truth every
operator↔Simard channel uses):
  - enriched prompt = base meeting_system.md prompt + "\n\n" + live OODA
    context (blank-line-separated suffix), regardless of base asset presence;
  - recalled graph-memory facts reach the prompt verbatim;
  - every OODA section (meetings, decisions, goals, operator, projects,
    research, improvements) is carried through;
  - recall failures PROPAGATE out of both build_enriched_meeting_system_prompt
    and build_live_meeting_context (PHILOSOPHY.md: no silent degradation).
Adds an erroring_bridge() test helper to drive the no-degradation cases.

signal_conversation::tests_continuity — pin that a per-operator make_backend
failure (recall/memory wiring or enriched-prompt build failing) PROPAGATES out
of run_continuous on a real turn and leaves no half-persisted conversation,
matching the SimardResult<MeetingBackend> closure contract.

Verified: cargo build --features signal; cargo test --features signal
signal_conversation (89 passed); cargo test operator_commands_meeting::
(71 passed); cargo clippy --features signal --all-targets -- -D warnings.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…access

enriched_prompt_appends_live_context_after_base builds the live OODA
context twice (once inside build_enriched_meeting_system_prompt, once
directly) and compares them as a blank-line-separated suffix. Because
build_live_meeting_context resolves the operator name from the
process-global SIMARD_OPERATOR_NAME env var, a concurrent env-mutating
test landing between the two builds made the two contexts diverge and
the suffix check flake (seen on the push-triggered verify run; the
pull_request run passed).

Join the cognitive_memory serial group and hold ENV_MUTEX while pinning
SIMARD_OPERATOR_NAME to a deterministic (unset) state, matching every
other env-touching test in this module, so both builds observe identical
env and never interleave with a mutator.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

📊 Coverage Summary

Generated by cargo llvm-cov --workspace --summary-only (nightly, excluding test files)

Module Lines Covered Coverage
Total 136997 113479 82.8%

Coverage data from CI run. Test files matching tests?/ are excluded from line counts.

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.

1 participant