Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
90eaf5b
Close sqlite connections after each store operation (Windows file locks)
sdgsfh Sep 1, 2026
3e21da4
Close sqlite connections after each store operation (Windows file locks)
sdgsfh Sep 1, 2026
541c8f7
Close sqlite connections after each store operation (Windows file locks)
sdgsfh Sep 1, 2026
3743d82
Close sqlite connections after each store operation (Windows file locks)
sdgsfh Sep 1, 2026
ff38dd1
Close sqlite connections after each store operation (Windows file locks)
sdgsfh Sep 1, 2026
9532a32
Close sqlite connections after each store operation (Windows file locks)
sdgsfh Sep 1, 2026
1546692
Stream offline provider fallback instead of pre-loop research gate
sdgsfh Sep 1, 2026
92249cc
Always provider-summarize count-based history chunks
sdgsfh Sep 1, 2026
4402813
test: align scratchpad lifecycle assertions with retired scratchpad.md
sdgsfh Sep 1, 2026
25dea55
test: wire archival provider slots in real-turn archival test
sdgsfh Sep 1, 2026
c71c086
test: expectedFailure for turn-driven adaptive workflow tests (retire…
sdgsfh Sep 1, 2026
6c50f98
Retry context-overflow recovery even when compaction cannot shrink fu…
sdgsfh Sep 1, 2026
4bb6d19
test: make structured call-site source checks path-robust and drift-free
sdgsfh Sep 1, 2026
046db6e
Mention raw records in query_session_records tool description
sdgsfh Sep 1, 2026
978cf56
Describe executable closure in the default agent rules template
sdgsfh Sep 1, 2026
8fafd43
registry: stop enforcing mode/internal hiding at dispatch time
sdgsfh Sep 1, 2026
bd0b8bc
research_workflow: scaffold placeholder scratchpad.md on state load
sdgsfh Sep 1, 2026
df62518
test: align scratchpad existence assertion with retired scratchpad.md
sdgsfh Sep 1, 2026
0b19b2b
Align core prompt block and MCP index heading with summary-index spec
sdgsfh Sep 1, 2026
9b029ff
research recording: restore record_artifact persistence and query_mem…
sdgsfh Sep 1, 2026
ab7b14f
Store executed tool calls as structured tool_result conversation events
sdgsfh Sep 1, 2026
23e3037
research: observe tool results into research memory; conservative pes…
sdgsfh Sep 1, 2026
3eabde3
Preserve original mixed and EOF line endings
sdgsfh Sep 1, 2026
1fab9e1
Restore query_memory rich payload, artifact recording, migration impo…
sdgsfh Sep 1, 2026
29c588b
research: capture blueprint drafts post-turn and make inherited archi…
sdgsfh Sep 1, 2026
349e808
Merge cluster E (infra/streaming/context/prompt drift fixes) into int…
sdgsfh Sep 1, 2026
e85ceec
Merge cluster B (query_memory rich payload, artifact recording, migra…
sdgsfh Sep 1, 2026
c197919
Merge cluster A (registry dispatch/schema separation, scratchpad scaf…
sdgsfh Sep 1, 2026
e5181fb
Reconcile A/B merge in query_memory and record_artifact
sdgsfh Sep 1, 2026
a633a7b
Merge cluster D (research loop observer, blueprint capture, refresh w…
sdgsfh Sep 1, 2026
c82e896
Revert cluster D's scratchpad assertFalse flip (df62518)
sdgsfh Sep 1, 2026
d0280a4
Merge cluster C (scratchpad lifecycle test alignment, archival wiring…
sdgsfh Sep 1, 2026
f00017b
Reconcile cluster C test edits with integrated code
sdgsfh Sep 1, 2026
0a11cb5
Review fixes: restore run_agent.py EOF CRLF, refresh stale expectedFa…
sdgsfh Sep 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 121 additions & 5 deletions agent_runtime/context_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,12 @@ def _trim_text_to_budget(self, text: str, token_budget: int) -> str:
kept.append("... [truncated]")
return "\n".join(line for line in kept if line).strip()

def _summarize_bounded_text_with_provider(self, *, purpose: str, text: str, token_budget: int) -> str:
def _summarize_bounded_text_with_provider(self, *, purpose: str, text: str, token_budget: int, force_provider: bool = False) -> str:
"""Ask the configured provider for one already-bounded source summary."""
source = str(text or "").strip()
if not source:
return ""
if self.estimate_tokens(source) <= token_budget:
if not force_provider and self.estimate_tokens(source) <= token_budget:
return source

if not isinstance(self.provider, OfflineProvider):
Expand Down Expand Up @@ -201,7 +201,7 @@ def _summarize_bounded_text_with_provider(self, *, purpose: str, text: str, toke
rendered = "\n".join("- %s" % item for item in bullets if item)
return self._trim_text_to_budget(rendered or shorten(source, token_budget * 4), token_budget)

def _summarize_with_provider(self, *, purpose: str, text: str, token_budget: int) -> str:
def _summarize_with_provider(self, *, purpose: str, text: str, token_budget: int, force_provider: bool = False) -> str:
"""Compress text through bounded provider calls and concatenate chunk summaries."""
chunks = split_text_by_token_budget(
text,
Expand All @@ -215,13 +215,15 @@ def _summarize_with_provider(self, *, purpose: str, text: str, token_budget: int
purpose=purpose,
text=chunks[0],
token_budget=token_budget,
force_provider=force_provider,
)
summaries = []
for index, chunk in enumerate(chunks, start=1):
summary = self._summarize_bounded_text_with_provider(
purpose="%s chunk %s/%s" % (purpose, index, len(chunks)),
text=chunk,
token_budget=token_budget,
force_provider=force_provider,
)
if summary:
summaries.append(summary)
Expand Down Expand Up @@ -432,10 +434,14 @@ def _summarize_history_chunks(self, older_messages: Sequence[Dict[str, object]])
chunks = self._chunk_history_by_count(older_messages, chunk_count=chunk_count)
summaries = []
for chunk_index, chunk in enumerate(chunks, start=1):
# Count-based history chunks are always provider-summarized, even
# when a chunk already fits the token budget, so every chunk keeps
# a uniform compact research-progress-report shape.
summary = self._summarize_with_provider(
purpose="conversation history chunk %s/%s" % (chunk_index, len(chunks)),
text=self._format_history_for_summary(chunk),
token_budget=per_chunk_budget,
force_provider=True,
)
summaries.append(summary)
return summaries
Expand Down Expand Up @@ -1701,8 +1707,9 @@ def query_memory(
limit=result_limit * max(1, len(research_log_types) or 1),
)

research_only = bool(research_log_types) or saw_research_type_filter
raw_hits: Dict[str, object] = {}
if not research_log_types and not saw_research_type_filter:
if not research_only:
raw_hits = self.memory_manager.query_memory_sources(
query,
project_slug,
Expand All @@ -1714,12 +1721,28 @@ def query_memory(
dynamic_hits = list(raw_hits.get("dynamic_hits") or [])
session_record_hits = list(raw_hits.get("session_record_hits") or [])
knowledge_hits = list(raw_hits.get("knowledge_hits") or [])
session_hits = [
dict(item)
for item in session_record_hits
if str(item.get("record_type") or "") == "message"
]
event_hits: List[Dict[str, object]] = []
if not research_only and self.session_store is not None:
try:
event_hits = self.session_store.search_conversation_events(
query,
limit=result_limit,
project_slug=project_slug,
)
except Exception:
event_hits = []

ranked_lists = [self._normalize_research_hits(research_log_hits)]
if not research_log_types and not saw_research_type_filter:
if not research_only:
ranked_lists.extend(
[
self._normalize_session_record_hits(session_record_hits),
self._normalize_event_hits(event_hits),
self._normalize_dynamic_hits(dynamic_hits),
self._normalize_knowledge_hits(knowledge_hits),
]
Expand Down Expand Up @@ -1760,6 +1783,14 @@ def query_memory(
"record_type": str(metadata.get("record_type") or ""),
"archive_path": str(metadata.get("archive_path") or ""),
}
elif source == "session-event":
result["local_context"] = self._build_session_context_window(item, query)
result["source_refs"] = {
"session_id": str(metadata.get("session_id") or ""),
"record_id": "event:%s" % str(metadata.get("event_id") or ""),
"record_type": str(metadata.get("event_kind") or "event"),
"archive_path": "",
}
elif source == "dynamic":
result["local_context"] = self._build_dynamic_context_window(item, query)
result["source_refs"] = {
Expand All @@ -1779,6 +1810,49 @@ def query_memory(
result["source_refs"] = dict(metadata)
results.append(result)

compressed_windows: List[Dict[str, object]] = []
for item in merged[: max(1, result_limit * 2)]:
source = str(item.get("source") or "")
metadata = dict(item.get("metadata") or {})
if source == "research-log":
window_text = self._build_research_context_window(item, query)
elif source in {"session", "session-record", "session-event"}:
window_text = self._build_session_context_window(item, query)
elif source == "dynamic":
window_text = self._build_dynamic_context_window(item, query)
elif source == "knowledge":
window_text = self._build_knowledge_context_window(item, query)
else:
window_text = str(item.get("text") or "")
compressed_windows.append(
{
"key": str(item.get("key") or ""),
"source": "research-artifact" if source == "research-log" else source,
"title": str(item.get("title") or ""),
"summary": str(
metadata.get("summary") or metadata.get("exact_excerpt") or item.get("text") or ""
),
"window_excerpt": window_text,
}
)

summary_lines: List[str] = []
for item in merged[: max(1, result_limit)]:
metadata = dict(item.get("metadata") or {})
label = str(
metadata.get("record_type")
or metadata.get("artifact_type")
or metadata.get("event_kind")
or item.get("source")
or "memory"
)
excerpt = str(
metadata.get("exact_excerpt") or metadata.get("summary") or item.get("text") or ""
).strip()
line = "[%s] %s" % (label, str(item.get("title") or ""))
summary_lines.append("%s\n%s" % (line, excerpt) if excerpt else line)
summary = "\n\n".join(line for line in summary_lines if line.strip())

locations: Dict[str, object] = {}
if project_slug:
locations["project_research_log"] = self.paths.project_research_log_file(project_slug).relative_to(self.paths.home).as_posix()
Expand All @@ -1787,13 +1861,55 @@ def query_memory(
locations["active_session"] = self.paths.session_dir(session_id).relative_to(self.paths.home).as_posix()
locations["session_index"] = self.paths.sessions_db.relative_to(self.paths.home).as_posix()

research_hits_payload: List[Dict[str, object]] = []
for item in research_log_hits:
hit_metadata = dict(item.get("metadata") or {})
record_type = str(item.get("type") or hit_metadata.get("record_type") or "research_note")
exact_excerpt = str(hit_metadata.get("exact_excerpt") or item.get("content_inline") or "")
raw_text = str(hit_metadata.get("raw_text") or item.get("content") or "")
retrieval_mode = str(hit_metadata.get("retrieval_mode") or "").strip() or "research_index"
title = str(item.get("title") or "")
research_hits_payload.append(
{
"id": str(item.get("id") or ""),
"source": "research-artifact",
"type": record_type,
"title": title,
"content": raw_text,
"exact_excerpt": exact_excerpt,
"retrieval_mode": retrieval_mode,
"score": float(item.get("score") or 0.0),
"project_slug": str(item.get("project_slug") or ""),
"session_id": str(item.get("session_id") or ""),
"source_refs": list(item.get("source_refs") or []),
"created_at": str(item.get("created_at") or ""),
}
)

return {
"query": query,
"scope": {
"project_slug": project_slug or "",
"all_projects": bool(all_projects),
"types": research_log_types,
},
"project_scope": "all-projects" if all_projects else str(project_slug or ""),
"all_projects": bool(all_projects),
"types": research_log_types,
"channels": [str(item) for item in list(channels or [])],
"channel_mode": normalized_channel_mode,
"limit_per_channel": result_limit,
"prefer_raw": bool(prefer_raw),
"summary": summary,
"results": results,
"compressed_windows": compressed_windows,
"sources": [dict(item) for item in merged],
"research_log_hits": research_log_hits,
"research_hits": research_hits_payload,
"dynamic_hits": self._serialize_dynamic_hit_rows(dynamic_hits),
"session_hits": session_hits,
"event_hits": event_hits,
"knowledge_hits": knowledge_hits,
"graph_hits": [],
"raw_record_locations": locations,
}
4 changes: 3 additions & 1 deletion agent_runtime/prompt_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ def build_system_prompt(
) -> str:
"""Build the system prompt for a conversation turn."""
lines = [
"You are Moonshine: an independent mathematical and technical researcher with explicit evidence, project context, and auxiliary tool support.",
"You are Moonshine: an independent mathematical and technical researcher with explicit evidence, canonical workspace, and auxiliary tool support.",
"Carry the current project or conversation forward directly rather than narrating it from the outside.",
"Use retrieval when prior context, decisions, or previous work may change the answer.",
"Think and reason in the assistant turn itself; use tools and files to support the work rather than to replace the work.",
"Canonical workspace files and explicit persistence or verification tool calls are the durable state-change boundary.",
"Tool schemas are attached to each main model call.",
"When a task matches a listed skill's usage guidance, load that skill with `load_skill_definition` before relying on its workflow, unless the step is trivial or the full definition is already in context.",
"When a brief summary is not enough, load the full agent, skill, tool, or MCP definition explicitly.",
"Use relevant tools and MCP tools when they materially help retrieval, file inspection, verification, experiments, or external context; do not rely on free-text claims when an available tool can provide evidence.",
"Skills provide detailed working methods; tools provide executable actions.",
]
Expand Down
12 changes: 12 additions & 0 deletions agent_runtime/research_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ def append_records(self, project_slug: str, records: Sequence[Dict[str, object]]
append_jsonl(self.log_path(project_slug), record)
self.rebuild_markdown_views(project_slug)
self._sync_blueprint_markdown(project_slug)
self._sync_blueprint_verified_markdown(project_slug)
self.rebuild_index(project_slug)
return created_records

Expand Down Expand Up @@ -397,6 +398,17 @@ def _sync_blueprint_markdown(self, project_slug: str) -> None:
text = read_text(self.markdown_path(project_slug), default="")
atomic_write(self.paths.project_blueprint_file(project_slug), text.rstrip() + ("\n" if text.strip() else ""))

def _sync_blueprint_verified_markdown(self, project_slug: str) -> None:
"""Keep workspace/blueprint_verified.md as the readable verification-record mirror.

The seeded placeholder is preserved until the project actually has
verification records to publish.
"""
text = read_text(self.paths.project_research_log_type_file(project_slug, "verification"), default="")
if not text.strip():
return
atomic_write(self.paths.project_blueprint_verified_file(project_slug), text.rstrip() + "\n")

def _mirror_verified_conclusion(self, project_slug: str, record: Dict[str, object]) -> None:
if self.knowledge_store is None:
return
Expand Down
Loading