feat: redesign runner memory tiers#173
Conversation
Co-authored-by: Son Dao <son.dao@katalon.com>
| - Additional memory files: `/workspace/memory/` and `/workspace/memory/<repo>/` — store one topic per file, list and grep as needed. | ||
| - Global memory: `/workspace/memory/README.md` — injected into every new session. Use only for rare cross-cutting Thor context, critical durable corrections, and workspace-wide operating notes. Keep short. | ||
| - Channel memory: `/workspace/memory/channels/<channel-id>.md` — injected into new Slack thread sessions when the correlation key is `slack:thread:<channel>/<ts>`. Use for durable channel/team preferences, recurring workflows, and channel-specific norms. | ||
| - Person memory: `/workspace/memory/people/<person-slug>.md` — injected into new sessions when the triggering actor resolves through `/workspace/config/thor.json` `users[]`. The slug prefers email local-part (for example `Son.Dao@example.com` → `people/son.dao.md`), else GitHub login, else sanitized name. Use for durable user preferences and identity context. |
There was a problem hiding this comment.
Updated the examples to use john.doe / acme in 3794801.
AI-generated — verify before acting. View Thor context
| - Global memory: `/workspace/memory/README.md` — injected into every new session. Use only for rare cross-cutting Thor context, critical durable corrections, and workspace-wide operating notes. Keep short. | ||
| - Channel memory: `/workspace/memory/channels/<channel-id>.md` — injected into new Slack thread sessions when the correlation key is `slack:thread:<channel>/<ts>`. Use for durable channel/team preferences, recurring workflows, and channel-specific norms. | ||
| - Person memory: `/workspace/memory/people/<person-slug>.md` — injected into new sessions when the triggering actor resolves through `/workspace/config/thor.json` `users[]`. The slug prefers email local-part (for example `Son.Dao@example.com` → `people/son.dao.md`), else GitHub login, else sanitized name. Use for durable user preferences and identity context. | ||
| - Repo-scoped context: not runner-injected memory. Use repo-local `AGENTS.md`, `CLAUDE.md`, and in-repo docs for repo/product facts, codebase conventions, runbooks, and anything humans should also see. Do not create or rely on `/workspace/memory/<repo>/README.md` for new repo context. |
There was a problem hiding this comment.
Never mention /README.md to avoid confusing LLM.
There was a problem hiding this comment.
Removed the <repo>/README.md mention from the prompt/docs in 3794801.
AI-generated — verify before acting. View Thor context
| - Repo-scoped context: not runner-injected memory. Use repo-local `AGENTS.md`, `CLAUDE.md`, and in-repo docs for repo/product facts, codebase conventions, runbooks, and anything humans should also see. Do not create or rely on `/workspace/memory/<repo>/README.md` for new repo context. | ||
|
|
||
| **Reading:** at the start of non-trivial sessions, check for relevant memory files by listing and grepping `/workspace/memory/`. For recovering prior context (Slack threads, past decisions, earlier investigations), search `/workspace/worklog/` first — it is faster and more complete than scanning Slack history. When a prompt says "Previous session was lost" and points at a worklog note, read that note directly as the continuity artifact. | ||
| **Reading:** the runner injects global/channel/person memory on new sessions only. For additional Thor-only context, check relevant files under `/workspace/memory/`. For recovering prior context (Slack threads, past decisions, earlier investigations), search `/workspace/worklog/` first — it is faster and more complete than scanning Slack history. When a prompt says "Previous session was lost" and points at a worklog note, read that note directly as the continuity artifact. |
There was a problem hiding this comment.
LLM should search /workspace/runs/ as it has more useful information density than worklog. Too much noise there. For something non-trivial, not worth adding a run directory -> probably not worth being recalled anyway.
There was a problem hiding this comment.
Updated the reading guidance to prefer /workspace/runs/ for non-trivial recurring work, and kept /workspace/worklog/ only for continuity/audit use in 3794801.
AI-generated — verify before acting. View Thor context
| | Person | `/workspace/memory/people/<person-slug>.md` | Yes, when the trigger actor resolves through `/workspace/config/thor.json` | Durable user preferences, identity context, preferred follow-up style, stable ownership hints. | | ||
| | Repo context | Repo-local `AGENTS.md`, `CLAUDE.md`, and docs | Delegated to OpenCode/repo files | Product facts, codebase conventions, runbooks, and anything humans should review in git. | | ||
|
|
||
| The runner no longer injects `/workspace/memory/<repo>/README.md`. Keep |
There was a problem hiding this comment.
Not useful. Just not mention /README.md at all because after this PR, we will drop that feature completely.
There was a problem hiding this comment.
Removed that <repo>/README.md mention from docs/feat/memory.md in 3794801.
AI-generated — verify before acting. View Thor context
| Person memory filenames are deterministic: | ||
|
|
||
| 1. email local-part from `users[].email`, lowercased and sanitized (for example | ||
| `Son.Dao@example.com` → `people/son.dao.md`) |
There was a problem hiding this comment.
Updated the examples to john.doe@example.com / acme@example.com in 3794801.
AI-generated — verify before acting. View Thor context
| function sanitizePersonSlugPart(value: string): string { | ||
| return value | ||
| .trim() | ||
| .toLowerCase() | ||
| .replace(/[^a-z0-9._-]+/g, "-") | ||
| .replace(/^[._-]+|[._-]+$/g, "") | ||
| .replace(/[-_.]{2,}/g, (match) => match[0] ?? "-"); | ||
| } | ||
|
|
||
| function personSlug(user: UserRecord): string | undefined { | ||
| const emailLocalPart = user.email.split("@")[0]; | ||
| return ( | ||
| sanitizePersonSlugPart(emailLocalPart ?? "") || | ||
| sanitizePersonSlugPart(user.github ?? "") || | ||
| sanitizePersonSlugPart(user.name) | ||
| ); | ||
| } |
There was a problem hiding this comment.
Keep this simple. email should be safe for file path anyway. Also: user email only, skip person memory logic if no email.
There was a problem hiding this comment.
Simplified the logic to use only the lowercased email local-part from the resolved user record. If the actor does not resolve through users[], person memory is skipped. Pushed in 3794801.
AI-generated — verify before acting. View Thor context
Co-authored-by: Son Dao <son.dao@katalon.com>
There was a problem hiding this comment.
Pull request overview
This PR redesigns the runner’s injected “durable memory” model by replacing per-repo memory injection with three tiers (global, Slack channel, and person), and updates both runner behavior and documentation to match.
Changes:
- Update runner bootstrap prompt injection to read global memory, optional Slack channel memory (by
slack:thread:<channel>/<ts>correlationKey), and optional person memory (by resolved triggering actor). - Add/expand runner tests covering channel memory injection and Slack/GitHub actor → person memory resolution.
- Document the new memory tiers plus read/write policy across repo docs and OpenCode build prompt guidance.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| README.md | Updates ops guidance to point to the new memory tiers and clarifies repo context belongs in-repo. |
| packages/runner/src/trigger.test.ts | Expands test coverage to assert global/channel/person memory injection behavior and actor resolution paths. |
| packages/runner/src/index.ts | Implements the new memory-tier bootstrap behavior and triggering-user resolution changes. |
| docs/feat/memory.md | Adds a concise spec of memory tiers and runner read/write policy. |
| docs/feat/agent-protocols.md | Links protocols doc to the new memory model document. |
| docker/opencode/config/agents/build.md | Updates prompt guidance to reflect global/channel/person memory and repo-local context policy. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const slackThread = parseSlackThreadCorrelationKey(correlationKey); | ||
| if (!slackThread) return undefined; | ||
| const path = `${memoryDir}/channels/${slackThread.channelId}.md`; | ||
| return { channelId: slackThread.channelId, path, content: readMemoryFile(path) }; |
There was a problem hiding this comment.
Noted. I confirmed with the original requester and we are intentionally not adding another code safeguard here because the Slack channel id comes from Thor's signed Slack webhook intake path rather than arbitrary end-user text. I recorded that decision in the task plan/decision log.
AI-generated — verify before acting. View Thor context
Co-authored-by: Son Dao <son.dao@katalon.com>
|
@copilot resolve the merge conflicts in this pull request |
Resolved and committed the merge conflicts in |
Summary
Testing
AI-generated — verify before acting. View Thor context