Previously workspace/.picoclaw/worktrees/ which created redundant nesting since workspace is already under ~/.picoclaw/. The dot prefix hides the directory from normal project listings. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
46 KiB
CLAUDE.md
Project: picoclaw
Go-based AI agent with multi-channel messaging (Telegram, Discord, Slack, etc.) and a Telegram Mini App UI.
Build & Test
go build ./...
go test ./...
go vet ./...
Lint: golangci-lint run
Plan Mode
- Interview tool filtering:
interviewAllowedToolsinpkg/agent/loop.gois the single source of truth for tools available during interview/review phases. BothfilterInterviewTools(strips definitions before LLM call) andisToolAllowedDuringInterview(argument-level gating) reference this map. - History clear:
/plan start clearwipes session history and summary on transition to executing. The Mini App review UI offers two sliders: standard approve and approve-with-clear.
Security TODOs
Log Fields masking: Done.SanitizeFields()inpkg/logger/logger.gomasks keys matchingtoken,key,secret,password,authorization,credential. Applied inRecentLogs()andwsLogs()stream.
Known Gaps
- Mini App log viewer has no frontend tests:
renderLogs()inpkg/miniapp/static/index.htmlis inline vanilla JS with no unit/E2E test coverage. Backend (Go) tests coverRecentLogs,SanitizeFields, and JSON serialization, but nothing verifies the JS rendering. This allowed the Fields display bug (fields sent but not rendered) to ship undetected. - No human intervention for heartbeat worktrees: Heartbeat sessions create git worktrees (
.worktrees/heartbeat-YYYYMMDD/) but there is no CLI or Mini App command to list, inspect, or manually dispose them. Need a/plan worktreescommand (or similar) that shows active worktrees with branch/commit info and allows manual merge/dispose.PruneOrphanedon startup only removes directories without auto-committing first, so uncommitted changes in orphaned worktrees are silently lost.
Memory Optimization Candidates
Reviewed 2026-02-24 on branch
memory-optimization-review. False positives included intentionally. Legend: ðŽ High / ð¡ Medium / ð¢ Low
A. ããããã¹ã§ã®æååçµå (strings.Builder æªäœ¿çš)
| éèŠåºŠ | ãã¡ã€ã« | è¡ | å 容 |
|---|---|---|---|
| ðŽ | pkg/tools/web.go |
73-85 | BraveSearchProvider.Search() â slice append + Join ã Builder ã« |
| ðŽ | pkg/tools/web.go |
155-167 | TavilySearchProvider.Search() â åäžãã¿ãŒã³ |
| ðŽ | pkg/tools/web.go |
211-254 | DuckDuckGoSearchProvider.extractResults() â ã«ãŒãå
append+Join |
| ðŽ | pkg/tools/web.go |
592-617 | WebFetchTool.extractText() â cleanLines ã Builder ã§ |
| ðŽ | pkg/skills/loader.go |
234-250 | BuildSkillsSummary() â []string + Join ã§ XML çµã¿ç«ãŠ (èŠçŽ æ°Ãã¢ãã±ãŒã·ã§ã³) â Builder ãž |
| ðŽ | pkg/channels/telegram.go |
789-806 | extractCodeBlocks() â codes ã¹ã©ã€ã¹ç¡å®¹é + ReplaceAllStringFunc ã® fmt.Sprintf |
| ðŽ | pkg/channels/telegram.go |
813-830 | extractInlineCodes() â åäžãã¿ãŒã³ |
| ð¡ | pkg/agent/context.go |
247 | BuildSystemPrompt() â systemPrompt += ã§é£çµ â Builder ãž |
| ð¡ | pkg/logger/logger.go |
241-246 | formatFields() â parts slice + Join â Builder ãž |
| ð¡ | pkg/skills/loader.go |
217-225 | LoadSkillsForContext() â parts + Join â Builder ãž |
| ð¡ | pkg/channels/discord.go |
162-168 | appendContent() â + æŒç®åã§çµå â Builder ãž |
| ð¡ | pkg/channels/slack.go |
234-272 | handleMessageEvent() â ã«ãŒãå
æååé£çµ â Builder ãž |
| ð¢ | pkg/git/worktree.go |
61-63 | SanitizeBranchName() â strings.ReplaceAll ã«ãŒã |
B. ã¹ã©ã€ã¹ã®äºå容éç¢ºä¿æŒã
| éèŠåºŠ | ãã¡ã€ã« | è¡ | å 容 |
|---|---|---|---|
| ð¡ | pkg/tools/toolloop.go |
87-96 | RunToolLoop() â normalizedToolCalls / toolNames ã make([]T, 0, æšå®å€) ã« â å®è£
æžã¿ (æ¢ã«ã³ãŒãäžã§å®¹éãã³ãä»ã) |
| ð¡ | pkg/config/config.go |
628 | findMatches() â var matches []ModelConfig â 容éãã³ããä»äž |
| ð¡ | pkg/config/migration.go |
48 | ConvertProvidersToModelList() â result ã« make([]ModelConfig, 0, 20) |
| ð¡ | pkg/skills/registry.go |
183 | SearchAll() â merged ã« make([]SearchResult, 0, len(regs)*limit) |
| ð¡ | pkg/skills/loader.go |
73 | ListSkills() â skills ã« make([]SkillInfo, 0, 20) çšåºŠ |
| ð¡ | pkg/channels/telegram.go |
832-861 | extractMarkdownTables() â out ã¯å®è£
æžã¿ (make([]string, 0, len(lines)))ãtables (L835) ã®ã¿å®¹éãã³ãæªå¯Ÿå¿ |
| ð¢ | pkg/skills/search_cache.go |
42-43 | NewSearchCache() â entries map / order slice ã« maxEntries ããã³ã |
| ð¢ | pkg/agent/session_tracker.go |
121 | ListActive() â result ã¹ã©ã€ã¹ã«å®¹éãã³ã â é€å€: ã¢ã¯ãã£ãã»ãã·ã§ã³æ°ãäºåäžæã§éçèŠç©ããäžå¯ |
C. äžèŠãª []byte â string 倿 / éè€å€æ
| éèŠåºŠ | ãã¡ã€ã« | è¡ | å 容 |
|---|---|---|---|
| ðŽ | pkg/channels/telegram.go |
1071-1111 | wrapByDisplayWidth() â ã«ãŒãå
ã§ string(r) (runeâstring) ãæ¯ã€ãã¬ãŒã·ã§ã³å®è¡ |
| ð¡ | pkg/tools/web.go |
545-562 | WebFetchTool.Execute() â string(body) ãæå€§5ååŒã³åºã â 1åã«éçŽ |
| ð¡ | pkg/tools/web.go |
289 | PerplexitySearchProvider.Search() â string(payloadBytes) + strings.NewReader â bytes.NewReader ãçŽæ¥äœ¿çš |
| ð¢ | pkg/utils/string.go |
50 | wrapLine() â ASCII äž»äœãªã®ã« []rune(line) |
| ð¢ | pkg/utils/string.go |
100 | Truncate() â é·ã確èªåã« []rune(s) |
| ð¢ | pkg/git/worktree.go |
71-75 | SanitizeBranchName() â ASCII åãè©°ããªã®ã« []rune |
| ð¢ | pkg/providers/claude_cli_provider.go |
133 | string(paramsJSON) åŸã« Builder ãžæžã蟌㿠â bytes.Write |
D. JSON Marshal/Unmarshal ã®éè€ã»ããããã¹
| éèŠåºŠ | ãã¡ã€ã« | è¡ | å 容 |
|---|---|---|---|
| ðŽ | pkg/providers/openai_compat/provider.go |
274, 362, 621 | ã¹ããªãŒãã³ã°ã«ãŒãå ã§ããŒã«åŒæ°ãè€æ°å Unmarshal |
| ð¡ | pkg/providers/anthropic/provider.go |
213 | json.Unmarshal(tu.Input, &args) â map ã«ãµã€ãºãã³ããªã |
| ð¡ | pkg/providers/codex_cli_provider.go |
154-155 | ããŒã«å®çŸ©ã«ãŒãå
ã§ json.Marshal(parameters) |
E. 倧ã㪠struct ã®å€æž¡ã / ã«ãŒãå ã³ããŒ
| éèŠåºŠ | ãã¡ã€ã« | è¡ | å 容 |
|---|---|---|---|
| ðŽ | pkg/agent/session_tracker.go |
125 | ListActive() â *entry ãå€ã³ããŒã㊠append â ãã€ã³ã¿ slice ã« |
| ð¡ | pkg/session/manager.go |
98-100 | GetHistory() â messages å
šã³ã㌠(ã¹ã¬ããå®å
šã®ããæå³çãCOW æ€èš) |
| ð¡ | pkg/session/manager.go |
187-188 | Save() â messages å
šã³ã㌠(åäž) |
| ð¡ | pkg/skills/registry.go |
132-133 | SearchAll() â []SkillRegistry ãå
šã³ããŒããŠããããã¯è§£é€ |
| ð¢ | pkg/logger/logger.go |
88-92 | recent() â LogEntry ãå€ã³ããŒããŠè¿åŽ â ãã€ã³ã¿ slice æ€èš |
F. sync.Pool / ãããã¡åå©çšã®æ€èš
| éèŠåºŠ | ãã¡ã€ã« | è¡ | å 容 |
|---|---|---|---|
| ð¡ | pkg/tools/web.go |
592-617 | extractText() â HTML è§£æçš Builder ã sync.Pool ã§åå©çš |
| ð¡ | pkg/channels/telegram.go |
757-861 | Markdown å€æç³»é¢æ°çŸ€ â ã¡ãã»ãŒãžæ¯ã«å€æ°ã®ãããã¡ãçæ â Pool å |
| ð¢ | pkg/utils/download.go |
43 | DownloadToFile() â ãšã©ãŒèªã¿åãçš make([]byte, 512) â å
±æãããã¡ |
G. LRU / ã¢ã«ãŽãªãºã ã¬ãã«ã®æé©å
| éèŠåºŠ | ãã¡ã€ã« | è¡ | å 容 |
|---|---|---|---|
| ð¡ | pkg/skills/search_cache.go |
161 | moveToEndLocked() â slice slicing ã§ O(n) LRU æŽæ° â doubly-linked list ã§ O(1) ã« |
H. ããã±ãŒãžã¬ãã«å€æ°å (颿°åŒã³åºãã®ãã³ã«åçæ)
| éèŠåºŠ | ãã¡ã€ã« | è¡ | å 容 |
|---|---|---|---|
| ð¢ | pkg/utils/media.go |
18-19 | IsAudioFile() â audioExtensions / audioTypes ã¹ã©ã€ã¹ãæ¯åçæ â var ã« |
| ð¢ | pkg/skills/clawhub_registry.go |
114 | fmt.Sprintf("%d", limit) â strconv.Itoa(limit) |
H. éè€ strings.Split / Join (memory.go)
| éèŠåºŠ | ãã¡ã€ã« | è¡ | å 容 |
|---|---|---|---|
| ð¡ | pkg/agent/memory.go |
233, 285, 352, 381 | extractPhaseContent / GetPlanPhases / MarkStep / AddStep â åäž MEMORY.md ã颿°æ¯ã« Split â çµ±å or ãã£ãã·ã¥ |
èšèšã¬ãã«ã®æ ¹æ¬åå â ãèŠèœãšããã§ã¯ãªããæ§é çã«äžå¯é¿ããªåé¡
åå¥ã®æé©ååè£ã®å€ãã¯ãæžãã人ã®äžæ³šæã§ã¯ãªããèšèšäžã®éžæãç¹å®ã®ã¢ãã±ãŒã·ã§ã³ãã¿ãŒã³ãå¿ ç¶çã«åŒãèµ·ãããŠããããšãèªã¿åããã以äžã¯ãã®æ ¹æ¬åå ãèšèšã¬ãã«ã§æŽçãããã®ã
D-1. MemoryStore ãããã¡ã€ã« = æ£ãã®èšèšã§ãããŒã¹æžã¿è¡šçŸããã£ãã·ã¥ã§ããªã
MemoryStore ã®åã¡ãœããã¯ã»ãŒå
šå¡ã ReadLongTerm() â strings.Split() â scan â strings.Join() ãç¬ç«ããŠå®è¡ãããGetMemoryContext() ã1ååŒã¶ã ãã§ãå
éšã§ ReadLongTerm() ã3å以äžåŒã°ããé£éãèµ·ããã
GetMemoryContext()
ââ HasActivePlan() â ReadLongTerm() â ãã¡ã€ã«I/O
ââ GetPlanStatus() â ReadLongTerm() â ãã¡ã€ã«I/O
ââ GetPlanContext() â ReadLongTerm() â ãã¡ã€ã«I/O
ââ GetCurrentPhase() â ReadLongTerm() â ãã¡ã€ã«I/O
ââ GetTotalPhases() â ReadLongTerm() â ãã¡ã€ã«I/O
ãªããããªã£ãã: MEMORY.md ããŠãŒã¶ãŒãçŽæ¥ç·šéã§ããå€éšãã¡ã€ã«ãšããŠèšèšãããããããã¡ã€ã«ãåžžã«ææ°ã®æ£ããšããåæãæç«ããŠãããã€ã³ã¡ã¢ãªãã£ãã·ã¥ãæã€ãšå€éšç·šéãåæ ãããªããªãæããããããã£ãã·ã¥ãèªç¶ã«å°å ¥ã§ããªãã
èšèšäžã®éžæè¢: (a) content ãåŒæ°ãšããŠåãåãå
éš pure function 矀 + é«ã¬ãã«ã¡ãœããã ãã1å ReadLongTerm() ãåŒã¶ã(b) ãŠã©ããä»ããã£ãã·ã¥ (fsnotify)ã(c) ãšãŒãžã§ã³ãã«ãŒãå
ã§1ã¿ãŒã³ã«1åã ãèªããã¿ãŒã³ã¹ã³ãŒããã£ãã·ã¥ãã
D-2. FunctionCall.Arguments ã JSON æååã®ãŸãŸåãšããŠå®çŸ©ãããŠãã
// protocoltypes/types.go
type FunctionCall struct {
Name string `json:"name"`
Arguments string `json:"arguments"` // â ã¯ã€ã€ãã©ãŒããã (JSONæåå) ããã®ãŸãŸãã¡ã€ã³åã«
}
ããŒã«åŒæ°ã¯ã¯ã€ã€äž "arguments": "{\"key\":\"value\"}" ã®åœ¢ã§å±ããããã®åå®çŸ©ã¯ãã®æååããã®ãŸãŸä¿æããã䜿ãåŽã¯æ¯å json.Unmarshal([]byte(tc.Function.Arguments), &args) ããªããã°ãªããããããã¹ããªãŒãã³ã°ã«ãŒãå
ã®éè€ Unmarshal ã®æ ¹æ¬åå ã«ãªã£ãŠããã
察æ¯: ToolCall.Arguments map[string]any json:"-" ãšããããŒã¹æžã¿ãã£ãŒã«ãã¯ååšããããopenai_compat ã® streaming path ã§ã¯ãã® map[string]any ãã£ãŒã«ãã§ã¯ãªã Function.Arguments string ããçŽæ¥èªãã§ãããäž¡æ¹ã®ãã£ãŒã«ããäžéå端ã«å
±åããŠããã
D-3. ToolFunctionDefinition.Parameters ã map[string]any ã§ãã·ãªã¢ã©ã€ãºæžã¿åœ¢åŒãä¿æã§ããªã
type ToolFunctionDefinition struct {
Name string `json:"name"`
Description string `json:"description"`
Parameters map[string]any `json:"parameters"` // â ãããã€ããŒãžéããã³ã« Marshal ãå¿
èŠ
}
ããŒã«å®çŸ©ã¯ãšãŒãžã§ã³ãèµ·åæã«äžåºŠæ±ºãŸããå®è¡äžã¯å€åããªããããã map[string]any ãšããŠä¿æããŠãããããåãããã€ããŒãžã®éä¿¡ã®ãã³ã« json.Marshal â string 倿ãçºçãããjson.RawMessage ã«ããŠããã°ãäžåºŠ marshal ãããã€ãåããã®ãŸãŸè€æ°ãããã€ããŒãžæµããèšèšãå¯èœã«ãªãã
D-4. æ€çŽ¢ãããã€ããŒçŸ€ã«å ±éãã©ãŒãããæœè±¡ããªããåãæ¬ é¥ã3ç®æã«è€è£œãããŠãã
BraveSearchProvider, TavilySearchProvider, DuckDuckGoSearchProvider ã¯å
šãŠç¬ç«ããŠãçµæ â æååãã®å€æããžãã¯ãå®è£
ããŠãããå
±éã® ResultFormatter ã€ã³ã¿ãŒãã§ãŒã¹ã formatSearchResult(title, url, snippet string) ãã«ããŒããªããããåã []string + strings.Join ãã¿ãŒã³ã3ç®æã«ç¬ç«ããŠã³ããŒããããæé©åæŒãã3ç®æã«åæã«çºçããã
èšèšã®ç€ºå: ãããã€ããŒã® Search() æ»ãå€ã string ã«ããæ§é äœ ([]SearchResult) ã«ããŠããã©ãŒããããåŒã³åºãåŽã«ç§»è²ããèšèšãªãããã©ãŒãããããžãã¯ã¯1ç®æã§æžãã
D-5. Session.Messages ãå¯å€ã¹ã©ã€ã¹ã§ãèªã¿åãã«æ§é çãªå
šã³ããŒãå¿
èŠ
type Session struct {
Messages []providers.Message // â å¯å€ãappend ã§è¿œèšããã
}
func (sm *SessionManager) GetHistory(key string) []providers.Message {
history := make([]providers.Message, len(session.Messages))
copy(history, session.Messages) // â å®å
šã®ããã«å¿
é
return history
}
session.Messages 㯠append ã§è¿œèšãããå¯å€ã¹ã©ã€ã¹ã§ãå€éšããåç
§ãæž¡ããšå
éšç¶æ
ãå£ãããªã¹ã¯ãããããã®ãã GetHistory(), Save(), SetHistory() ã®å
šãŠã§ã³ããŒãå¿
èŠã«ãªããã³ã¡ã³ãã«ããto strictly isolate internal state from the caller's sliceããšæèšãããŠãããããã¯æå³çãªèšèšã ãã³ããŒã³ã¹ããæ§é çã«åºå®ããŠããã
代æ¿èšèš: ã¡ãã»ãŒãžãã°ã append-only ãªäžå€æ§é ([]*Message ã®ãªã³ã¯ãªã¹ãããã€ã³ããã¯ã¹ã§ç®¡çãããªã³ã°ãããã¡) ã«ããã°ãåç
§ã®å
±æãå®å
šã«ãªãã³ããŒãæé€ã§ããã
D-6. MemoryStore ã®ã¡ãœããå¢çãããã¡ã€ã«æäœåäœãã§åãããŠãããåŒã³åºãåŽãåæã§ããªã
// åŒã³åºãåŽã¯ content ãæãŠãªããããå
éšã§æ¯å ReadLongTerm() ãåŒã¶
phases := ms.GetPlanPhases() // ReadLongTerm() å
å
current := ms.GetCurrentPhase() // ReadLongTerm() å
å
status := ms.GetPlanStatus() // ReadLongTerm() å
å
å public ã¡ãœãããããã¡ã€ã«ãèªãã§ããŒã¹ããŠ1ã€ã®å€ãè¿ããåäœã§èšèšãããŠãããããåŒã³åºãåŽã¯è€æ°ã®å€ãå¿
èŠãªãšãã§ãã¡ãœãããè€æ°ååŒã¶ããéžæè¢ããªããcontent ãåãåã private 颿°çŸ€ (extractPhaseContent(content, phase) ãªã©) ã¯ååšããããpublic API ããã¯äœ¿ããªãã
ã³ãŒãã®ã«ãã â èŠèœãšãããããã¿ãŒã³é
äžèšã®åå¥çºèŠã暪æããŠèŠããšããã®ã³ãŒãããŒã¹ã«ç¹°ãè¿ãçŸãã7ã€ã®æ§é çãªã«ããããããæ°ããã³ãŒããæžããšãã»ã¬ãã¥ãŒãããšãã®ãã§ãã¯ãªã¹ããšããŠäœ¿ãã
1. ãå
ã«éããŠããçµåããã¿ãŒã³ ([]string + strings.Join)
// ã«ããã®ããæžãæ¹
var parts []string
for _, x := range items {
parts = append(parts, fmt.Sprintf("...%s...", x))
}
return strings.Join(parts, "\n")
var parts []string â ã«ãŒãå
append â æåŸã« strings.Join ãšãã3ã¹ãããã®æµããèŠãç®ãæŽçãããŠããããæ°ã¥ãã«ããããäžéã¹ã©ã€ã¹ãšæçµçµåã®2åã¢ãã±ãŒã·ã§ã³ãçºçãããstrings.Builder ã«äžæ¬åããã°1åã§æžããweb.go ã®æ€çŽ¢ãããã€ããŒ4ç®æãlogger.goãskills/loader.go ãªã©èš10ç®æä»¥äžã§èгå¯ãããã
2. ã倿ããŠããæž¡ãããã¿ãŒã³ ([]byte â string ã®æ©æž¡ã)
// ã«ããã®ããæžãæ¹
payload, _ := json.Marshal(body)
req, _ := http.NewRequest("POST", url, strings.NewReader(string(payload)))
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// []byte â string â io.Reader ãš2æ®µå€æ
json.Marshal 㯠[]byte ãè¿ãã®ã«ãçŽåŸã« string() ãžãã£ã¹ãã㊠strings.NewReader ã«æž¡ããbytes.NewReader(payload) ã§å€æãŒãã§æžããweb.go ã® Perplexity ãããã€ããŒãå CLI ãããã€ããŒã§èгå¯ãããã
3. ãã«ãŒãå ã§éçãªãã®ãæ¯åçæããã¿ãŒã³
// ã«ããã®ããæžãæ¹
for _, tool := range tools {
paramsJSON, _ := json.Marshal(tool.Parameters) // â ã«ãŒãå
Marshal
prompt += fmt.Sprintf("...", string(paramsJSON))
}
ã«ãŒãå ã§æ¯ã€ãã¬ãŒã·ã§ã³è¡ãããåŠçã®ãã¡ãå ¥åãå€ãããªããã®ãå«ãŸããŠããªãããçããå žåäŸïŒ
- ã«ãŒãå
ã§ã®
json.Marshal(åŒæ°ã宿°çãªãšã) - ã«ãŒãå
ã§ã®
string(rune)倿 (1æåãã€å€æ) - ã«ãŒãå ã§ã®ã¹ã©ã€ã¹/ããããªãã©ã«çæ
telegram.go ã® wrapByDisplayWidthãopenai_compat ã® streaming ã«ãŒããcodex ã® tool å®çŸ©ã«ãŒãã§èгå¯ãããã
4. ãé²è¡çã³ããŒãåºãããããã¿ãŒã³ (ã¹ã¬ããå®å šã®éå°é©çš)
// ã«ããã®ããæžãæ¹
func (m *Manager) GetHistory() []Message {
m.mu.RLock()
defer m.mu.RUnlock()
result := make([]Message, len(m.messages))
copy(result, m.messages) // â å
šä»¶ã³ããŒããŠããããã¯è§£é€
return result
}
䞊è¡å®å
šã®ãã slice å
šäœãé²è¡çã«ã³ããŒããã®ã¯æ£ããããã³ããŒç¯å²ãåŒã³åºãåŽã®å®éã®çšéããåºãããšããããèªã¿åãå°çšãªã sync.RWMutex + ãã€ã³ã¿è¿åŽ + immutable å¶çŽããŸã㯠Copy-on-Write ã§ä»£æ¿ã§ããå Žåããããsession/manager.go ã® GetHistoryã»Save ã§èгå¯ãããã
5. ããã¡ã€ã«ãèªããã³ã«ããŒã¹ããã¿ãŒã³ (ã¹ããŒãã¬ã¹ãªç¹°ãè¿ãããŒã¹)
// ã«ããã®ããæžãæ¹
func GetPlanPhases(content string) []string {
lines := strings.Split(content, "\n") // â åŒã³åºãæ¯ã«ãã«ã¹ãã£ã³
...
}
func MarkStep(content, step string) string {
lines := strings.Split(content, "\n") // â åã content ãå床ã¹ãã£ã³
...
}
åäžã®ãã¡ã€ã«å
容ãåãåãè€æ°ã®é¢æ°ãããããç¬ç«ã㊠strings.Split â ã¹ãã£ã³ â strings.Join ããŠãããåŒã³åºãåŽã§ããŒã¹æžã¿è¡šçŸïŒè¡ã¹ã©ã€ã¹ãªã©ïŒãä¿æããŠæž¡ãããããŒã¹çµæããã£ãã·ã¥ããèšèšã«ãããšè€æ°åã®ã¢ãã±ãŒã·ã§ã³ãåæžã§ãããmemory.go ã®4颿°ã§èгå¯ãããã
6. ãvar x []T ããå§ãŸã容éãªã appendããã¿ãŒã³
// ã«ããã®ããæžãæ¹
var result []ModelConfig // cap=0 ããéå§
for _, p := range providers {
result = append(result, ...) // åã
ã«åã¢ãã±ãŒã·ã§ã³
}
var x []T ã make([]T, 0) ã§å§ãŸããã«ãŒãå
ã§ append ãéããããœãŒã¹ã®é·ããäºåã«ããã£ãŠããå ŽåïŒå¥ã¹ã©ã€ã¹ã® lenã宿°äžéãªã©ïŒã¯ make([]T, 0, n) ã§åæå®¹éãäžããã°åã¢ãã±ãŒã·ã§ã³ããŒãã«ã§ãããèŠèœãšãããããçç±ã¯ãappend ã¯èªåã§äŒžã³ããã倧äžå€«ããšããç¿æ
£ãconfig/migration.goãskills/registry.goãskills/loader.go ã»ã6ç®æã§èгå¯ãããã
7. ãUnicode å®å šã®ããã®éå°ãª []rune 倿ããã¿ãŒã³
// ã«ããã®ããæžãæ¹
func Truncate(s string, max int) string {
runes := []rune(s) // â å
šæåã倿ããŠããé·ã確èª
if len(runes) <= max {
return s
}
return string(runes[:max])
}
æåæ°ãæ£ããæ°ããããã« []rune ãžå€æããã®ã¯æ£ãããããã â 倿åã« len(s) ã§ byte é·ããã§ãã¯ããŠæ©æ return ã§ããïŒASCII ãªã byte é· == rune é·ïŒãâ¡å®éã®å
¥åã ASCII äž»äœã§ããã° utf8.RuneCountInString + utf8.RuneError ãã§ãã¯ã§ã¢ãã±ãŒã·ã§ã³ãªãã«åŠçã§ããã[]rune(s) ã¯æååå
šäœãããŒãã«ã³ããŒãããããé·ãæååã§ã¯ç¡èŠã§ããªãã³ã¹ãã«ãªããutils/string.go ã®2颿°ãgit/worktree.go ã§èгå¯ãããã
ã¹ãã¬ãŒãžä¿è·èšèš â æžã蟌ã¿ã®é å»¶ã»ãããå
è¿œèš 2026-02-24ãmicroSDäžã§åäœããåæã§ã®FSæžãèŸŒã¿æé©åã
ã誰ããã®ããŒã¿ãå¿ èŠãšããããããã
çŸç¶ã®æ°žç¶åããŒã¿ãæ¶è²»è ãšæžã蟌ã¿é »åºŠã§æŽçãããšãæžã蟌ã¿ãé å»¶ã§ããäœå°ã倧ããç°ãªãã
| ããŒã¿ | ããã»ã¹å èªè | ããã»ã¹å€èªè | æžã蟌ã¿é »åºŠ(çŸç¶) | æå€±èš±å®¹åºŠ |
|---|---|---|---|---|
sessions/*.json |
AgentLoop (ã¿ãŒã³æ¯ GetHistory) |
ãªãïŒèµ·åæããŒãã®ã¿ïŒ | ã¡ãã»ãŒãžæ¯ | äžïŒäŒè©±æ¶å€±ã¯å°ããèŽåœã§ã¯ãªãïŒ |
state/stats.json |
StatusAPI, Mini AppïŒin-processïŒ | CLI cmd_status |
LLMåŒã³åºãæ¯ + ãŠãŒã¶ãŒã¡ãã»ãŒãžæ¯ | äœïŒæ°ä»¶ã®ãã¹ã¯èš±å®¹ïŒ |
memory/MEMORY.md |
AgentLoop (ã¿ãŒã³æ¯) | CLI, Mini App, å€éšãšãã£ã¿ | ã¹ãããå®äºæ¯ã»LLM edit_file | é«ïŒãã©ã³ç¶æ ã倱ããããšåŸ©åž°äžèœïŒ |
memory/YYYYMM/DD.md |
AgentLoopïŒãã©ã³ãªãæïŒ | å€éšãšãã£ã¿ | æ¥æ¬¡ããŒãè¿œèšæïŒäœé »åºŠïŒ | äœ |
éèŠãªèгå¯: ã»ãã·ã§ã³ãã¡ã€ã«ã¯ããã»ã¹å å°çšããŒã¿
sessions/*.json ã¯çšŒåäžã«å€éšããã»ã¹ãèªãŸãªããå¯äžã®å©çšã¿ã€ãã³ã°ã¯èµ·åæã® loadSessions()ãã€ãŸãæžã蟌ã¿ã®ç®çã¯ãã¯ã©ãã·ã¥ãªã«ããªãã ãã§ãããã¡ãã»ãŒãžæ¯ã®å³ææžã蟌ã¿ã¯éå°ã
åæ§ã« state/stats.json ããMini App ã CLI ã¯ããã»ã¹å
ã® Tracker.GetStats() çµç±ã§ã¡ã¢ãªããèªãããã¡ã€ã«ã¯ããã»ã¹åèµ·åæã®åŒãç¶ãå°çšã
æšå¥šæžãèŸŒã¿æŠç¥
sessions/*.json â Write-behind (ããŒãã£ãã©ã° + 宿ãã©ãã·ã¥)
AddFullMessage() â in-memory ã®ã¿æŽæ°ãdirty ãã©ã°ç«ãŠ
â
宿ã¿ã€ã㌠(5å) or ã¡ãã»ãŒãžæ°éŸå€ (20ä»¶)
ãŸãã¯ã·ã£ããããŠã³ãã㯠â Save()
- ãªã«ããªãŠã£ã³ããŠ: æå€§5å or 20ã¡ãã»ãŒãžå
- æžã蟌ã¿åæ°åæžç: äŒè©±é床次第ã ã 10ã50å
- å®è£
:
SessionManagerã«dirtyKeys map[string]bool+ ããã¯ã°ã©ãŠã³ããã©ãã·ã£ãŒgoroutine
state/stats.json â 宿ãã©ãã·ã¥ã®ã¿
RecordUsage() / RecordPrompt() â in-memory ã®ã¿æŽæ°
â
宿ã¿ã€ã㌠(5å) â save()
+ ã·ã£ããããŠã³ããã¯
- æå€±ãªã¹ã¯: æå€§5ååã®çµ±èšã«ãŠã³ãïŒèš±å®¹ç¯å²ïŒ
- æžã蟌ã¿åæ°åæžç: LLMåŒã³åºãé »åºŠ à 5å = æ°åãæ°çŸå
memory/MEMORY.md â ã¿ãŒã³ã¹ã³ãŒããã£ãã·ã¥ (æžã蟌ã¿ã¯å³æç¶æ)
æžã蟌ã¿ã¯çŸç¶éã峿ãèªã¿åãã®åé¡ã ã解決ããã
ãšãŒãžã§ã³ãã¿ãŒã³éå§ â content := ReadLongTerm() ã1åã ã
â content ãåŒæ°ãšããŠå
šãã«ããŒã«æž¡ã
(HasActivePlan(content), GetPlanStatus(content), ...)
ãšãŒãžã§ã³ãã¿ãŒã³çµäº â content ãã£ãã·ã¥ç Žæ£
- å€éšãšãã£ã¿ãšã®æŽå: ã¿ãŒã³å¢çã§ãªãã¬ãã·ã¥ãããã®ã§1ã¿ãŒã³ä»¥å ã®å€éšç·šéã®ã¿èŠéãïŒèš±å®¹ç¯å²ïŒ
- LLM ã® edit_file çµç±ã®æžã蟌ã¿: ãã¡ã€ã«ã·ã¹ãã ã«å³åº§ã«æžãããããæ¬¡ã¿ãŒã³ã§èªååæ
- èªã¿åãåæ°åæž: 1ã¿ãŒã³ããã
5åä»¥äž â 1å
microSD 寿åœãžã®åœ±é¿è©Šç®
äžè¬çãªäŒè©±ã»ãã·ã§ã³ïŒ1æéã60ã¡ãã»ãŒãžã10 LLMåŒã³åºã/åïŒã®å Žå:
| ããŒã¿ | çŸç¶ã®æžã蟌ã¿åæ°/æ | æ¹ååŸ | åæžç |
|---|---|---|---|
| sessions/*.json | ~60å (ã¡ãã»ãŒãžæ¯) | ~12å (5忝) | 80%æž |
| stats.json | ~660å (LLMåŒ+promptæ¯) | ~12å (5忝) | 98%æž |
| MEMORY.md | ã¹ãããæ°åïŒå€ãããïŒ | åå·Š | â |
| åèš | 720+ å/æ | ~24å/æ | 97%æž |
å®è£ äžã®æ³šæç¹
- ã·ã£ããããŠã³ããã¯å¿
é :
SIGTERM/SIGINTã§ dirty ãªããŒã¿ã匷å¶ãã©ãã·ã¥ããã©ãã·ã¥å€±ææã¯ãã°ã«èšé²ã - ã¯ã©ãã·ã¥åŸã®ãªã«ããª: dirty ããŒã¿ã倱ãããå Žåãã»ãã·ã§ã³å±¥æŽã¯æåŸã®ãã§ãã¯ãã€ã³ã以éãæ¶ããããŠãŒã¶ãŒãžã®éç¥ãå¿ èŠãæ€èšã
- ãã©ãã·ã¥äžã®ç«¶å: ãã©ãã·ã¥goroutineãš
Save()ã®åæåŒã³åºããé²ããããæ¢åã® mutex ãæµçšã - MEMORY.md ã®ã¿ãŒã³ãã£ãã·ã¥:
edit_fileããŒã«ã MEMORY.md ãæžã蟌ãã å Žåãåã¿ãŒã³å ã®ãã£ãã·ã¥ãç¡å¹åããä»çµã¿ãå¿ èŠïŒMemoryStore.InvalidateCache()ã edit_file ã®ã³ãŒã«ããã¯ããåŒã¶ãªã©ïŒãããããªããšåã¿ãŒã³å ã®åŸç¶ã®GetPlanStatus()ãªã©ãå€ããã£ãã·ã¥ãèªãã
RAM ãæœ€æ²¢ãªå Žåã®èšèšå€æŽ
察象ããã€ã¹ã¯ RAM 7GB / available 5GB è¶
ïŒäŸ: free -m ã§ available ~5260MBïŒã
ãã®åæãäžèšã®åæŠç¥ã«äžãã圱é¿ãæŽçããã
èªã¿åãã¬ã€ãã³ã·ã®å®æ
buff/cache ã 4.6GB çšåºŠãå ãããšããããšã¯ãOS ã®ããŒãžãã£ãã·ã¥ã空ã RAM ãã»ãŒå
šãŠäœ¿ãåã£ãŠããç¶æ
ãReadLongTerm() ã®è€æ°ååŒã³åºãã¯å®éã«ã¯ãã£ã¹ã¯ã¢ã¯ã»ã¹ããŠããªãïŒ2åç®ä»¥éã¯ããŒãžãã£ãã·ã¥ãããããã€ã¯ãç§ãªãŒããŒïŒã
èªã¿åãã®å®ã³ã¹ãã¯ããã£ã¹ã¯I/Oãã§ã¯ãªããsyscall + æåå Split/Join ã®ã¢ãã±ãŒã·ã§ã³ããã¿ãŒã³ã¹ã³ãŒããã£ãã·ã¥ã®äž»ãªå¹æã¯ã¬ã€ãã³ã·åæžããGC å§åã®è»œæžã«å€ããã
æžã蟌ã¿å¯¿åœã¯RAMã«åœ±é¿ãããªã
æžã蟌ã¿ã¯ O_SYNC ã§ã¯ãªããŠã os.Rename ã§ã¢ãããã¯ã«æžãããããã«ãŒãã«ã¯ã©ã€ãããã¯ãã£ãã·ã¥ãçµç±ããŠæçµçã« SD ã«æžããããŒãžãã£ãã·ã¥ãæžã蟌ã¿ãåžåããŠãæçµç㪠NAND ãžã®æžã蟌ã¿åæ°ã¯å€ãããªããæžã蟌ã¿åæžã®åªå
床ã¯å€ãããé«ãã
ã€ã³ã¡ã¢ãªè¡šçŸã®åžžé§ãçŸå®çã«ãªã
RAM ãéŒè¿«ããŠããªãå ŽåãMemoryStore ã« *ParsedPlan ããã£ãŒã«ããšããŠæãããèšèšïŒD-1, D-6 ã®è§£æ±ºçïŒã®ã¡ã¢ãªã³ã¹ãã¯ç¡èŠã§ãããMEMORY.md ãæ°KBãæ°åKB ã§ãã£ãŠããããŒã¹æžã¿æ§é äœãšããŠåžžé§ãããŠå·®ãæ¯ããªãã
// èšèšæ¡: MemoryStore ãããŒã¹æžã¿ç¶æ
ãä¿æ
type MemoryStore struct {
workspace string
memoryFile string
mu sync.RWMutex
cached *ParsedPlan // nil = æªããŒã
cachedAt time.Time
}
// edit_file ããŒã«ãæžã蟌ãã åŸã« InvalidateCache() ãåŒã¶ããšã§
// åã¿ãŒã³å
ã®åèªã¿èŸŒã¿ãããªã¬ãŒã§ãã
ããã«ãã GetMemoryContext() å
ã® ReadLongTerm() å€éåŒã³åºãåé¡ïŒD-1ïŒãšã
public ã¡ãœããã content ãé ãåé¡ïŒD-6ïŒãåæã«è§£æ¶ãããã
write-behind çªãããã«åºãããã
RAM ãååã«ãããããã»ãã·ã§ã³ããŒã¿ãé·æéã€ã³ã¡ã¢ãªã«ä¿æãããªã¹ã¯ããªãã write-behind ã®æŠç¥ãã5å or 20ä»¶ããã**ãã°ã¬ãŒã¹ãã«ã·ã£ããããŠã³æã®ã¿ + 30åã¿ã€ããŒã**ã«ç·©åããŠãã ã¯ã©ãã·ã¥æã®æå€±ïŒæå€§30åã®äŒè©±ïŒãšå®è£ ã®åçŽãã®ãã¬ãŒããªããšããŠèš±å®¹ã§ããå¯èœæ§ãããã ãããžã§ã¯ãã®å¯çšæ§èŠä»¶ã«å¿ããŠå€æããã
åªå å®è£ é ã®ä¿®æ£
RAM å¶çŽããªãåæã§ã®æšå¥šé :
stats.jsonã® write-behind â å®è£ ãæãåçŽïŒã¿ã€ããŒ1æ¬è¿œå ïŒãæžã蟌ã¿åæžçãæå€§ïŒ98%ïŒsessions/*.jsonã® write-behind â ã»ãã·ã§ã³åäœã® dirty ãã©ã° + ã·ã£ããããŠã³ããã¯MemoryStoreãžã®*ParsedPlanåžžé§ â D-1/D-6 ãæ ¹æ¬è§£æ±ºãèªã¿åãã¢ãã±ãŒã·ã§ã³ããŒãã«- ã¿ãŒã³ã¹ã³ãŒããã£ãã·ã¥ â 3 ãå®è£ ãããã°èªç¶ã«è§£æ±ºããããäžèŠã«ãªãå¯èœæ§ãã
æ¹ä¿®èšç» â ã¡ã¢ãªæé©åã®å®è£ ãã§ãŒãº
äœæ 2026-02-24ãã¬ãã¥ãŒçµæ (AãH + D-1ãD-6 + ã¹ãã¬ãŒãžä¿è·) ãå®è£ å¯èœãªåäœã«åå²ã åãã§ãŒãºã¯
go build ./... && go test ./... && go vet ./...ãéãç¶æ ã§å®çµããã
ãã§ãŒãº 0: æ©æ¢°çãªçœ®ãæã (äœãªã¹ã¯ã»é«ã«ãã¬ããž)
ç®ç: ã³ãŒãæ§é ãå€ãããåã颿°å ã§ãã¿ãŒã³ã眮ãæããã ãã®ä¿®æ£ãã¬ãã¥ãŒã容æã§ååž°ãªã¹ã¯ãæå°ã
0-1. strings.Builder 眮ãæã (ã«ããŽãª A æ®ã)
| ãã¡ã€ã« | 颿° | åªå 床 |
|---|---|---|
pkg/tools/web.go |
BraveSearchProvider.Search() L73-85 |
ðŽ |
pkg/tools/web.go |
TavilySearchProvider.Search() L155-167 |
ðŽ |
pkg/tools/web.go |
DuckDuckGoSearchProvider.extractResults() L211-254 |
ðŽ |
pkg/tools/web.go |
WebFetchTool.extractText() L592-617 |
ðŽ |
pkg/skills/loader.go |
BuildSkillsSummary() L234-250 |
ðŽ |
pkg/agent/context.go |
BuildSystemPrompt() L247 â += ã Builder ã« |
ð¡ |
pkg/logger/logger.go |
formatFields() L241-246 |
ð¡ |
pkg/skills/loader.go |
LoadSkillsForContext() L217-225 |
ð¡ |
pkg/channels/telegram.go |
extractCodeBlocks() L789-806 â codes ç¡å®¹é + ã«ãŒãå
fmt.Sprintf |
ðŽ |
pkg/channels/telegram.go |
extractInlineCodes() L813-830 â åäžãã¿ãŒã³ |
ðŽ |
pkg/channels/discord.go |
appendContent() L162-168 |
ð¡ |
pkg/channels/slack.go |
handleMessageEvent() L234-272 |
ð¡ |
0-2. ã¹ã©ã€ã¹äºå容é (ã«ããŽãª B æ®ã)
| ãã¡ã€ã« | å€æŽ |
|---|---|
pkg/skills/loader.go:73 |
make([]SkillInfo, 0) â make([]SkillInfo, 0, 20) |
pkg/config/config.go:628 |
var matches â make([]ModelConfig, 0, 4) |
pkg/config/migration.go:48 |
var result â make([]ModelConfig, 0, 20) |
pkg/skills/registry.go:183 |
var merged â make([]SearchResult, 0, len(regs)*limit) |
pkg/skills/search_cache.go:42-43 |
map/slice ã« maxEntries ãã³ã |
pkg/channels/telegram.go:832-861 |
extractMarkdownTables() â tables ã¹ã©ã€ã¹ã«å®¹éãã³ã |
0-3. byte/string 倿ã®åæž (ã«ããŽãª C)
| ãã¡ã€ã« | å€æŽ |
|---|---|
pkg/tools/web.go:289 |
strings.NewReader(string(payloadBytes)) â bytes.NewReader(payloadBytes) |
pkg/tools/web.go:545-562 |
è€æ°ã® string(body) â 1åã ã倿ããŠå€æ°ã«ä¿æ |
pkg/providers/claude_cli_provider.go:133 |
string(paramsJSON) â sb.Write(paramsJSON) |
pkg/utils/string.go:100 |
Truncate() â len(s) <= max ã§æ©æ return (ASCII fast path) |
pkg/utils/string.go:50 |
wrapLine() â åäž ASCII fast path |
pkg/git/worktree.go:71-75 |
[]rune â byte é·ãã§ãã¯ã§æ©æ return |
pkg/channels/telegram.go:1071-1111 |
wrapByDisplayWidth() â ã«ãŒãå
string(r) ã displayWidth ã®åŒæ°ã rune ã«å€æŽããŠæé€ |
0-4. ããã±ãŒãžå€æ°å (ã«ããŽãª H)
| ãã¡ã€ã« | å€æŽ |
|---|---|
pkg/utils/media.go:18-19 |
audioExtensions/audioTypes ã颿°å€ã® var ã« |
pkg/skills/clawhub_registry.go:114 |
fmt.Sprintf("%d", limit) â strconv.Itoa(limit) |
pkg/agent/memory.go:567, 663 |
regexp.MustCompile(...) ã€ã³ã©ã€ã³ â æ¢åããã±ãŒãžå€æ° reTaskLine (L469) ã«çœ®ãæã |
ã³ãããåäœ: 0-1, 0-2, 0-3, 0-4 ãããããåå¥ã³ãããã
ãã§ãŒãº 1: 倿ž¡ãã»ã³ããŒã®æé©å (ã«ããŽãª E)
ç®ç: struct ã®äžèŠãªã³ããŒãåæžãåã·ã°ããã£ãå€ããããåŒã³åºãåŽã®ä¿®æ£ãå¿ èŠã
| ãã¡ã€ã« | å€æŽ | 泚æ |
|---|---|---|
pkg/logger/logger.go:88-92 |
ãªã³ã°ãããã¡å
éšåã []*LogEntry ã«å€æŽ + visit(fn) ã¡ãœãã远å ãRecentLogs() ã visit ããŒã¹ã«æžãæã (ãã£ã«ã¿ã§åŒŸããšã³ããªã®ã³ããŒãæé€) |
push() æ¯ã«1ããŒãã¢ãã±ãŒã·ã§ã³å¢ã ããã°I/Oãã¹ãªã®ã§èš±å®¹ |
åé€ããé ç®:
session_tracker.go:125âTouch()ãããã¯ãªãã«ãã£ãŒã«ããçŽæ¥æŽæ°ããŠããã*entryå€ã³ã㌠(L125) ãå¯äžã®å®å šè£ 眮ããã€ã³ã¿è¿åŽã¯å®å šäžã®éè¡ãSessionEntry㯠~80ãã€ãã®å°ãã struct ã§ã³ããŒã³ã¹ããç¡èŠå¯èœãskills/registry.go:132-133âSkillRegistryã¯ã€ã³ã¿ãŒãã§ãŒã¹åã*SkillRegistry㯠pointer-to-interface ã¢ã³ããã¿ãŒã³ãã³ããŒã n à 16ãã€ã (n=2ã5) ã§ç¡èŠå¯èœã
ã³ããã: 1ã€ã«ãŸãšããã
ãã§ãŒãº 2: JSON ããããã¹ã®æé©å (ã«ããŽãª D)
ç®ç: ã¹ããªãŒãã³ã°ã«ãŒãå ã®éè€ Marshal/Unmarshal ãæé€ã
2-1. openai_compat streaming ã® Arguments éè€ Unmarshal
pkg/providers/openai_compat/provider.go L274, 362, 621
â ã¹ããªãŒã å®äºæã«1åã ã Unmarshal ããããå¶åŸ¡ãããŒãæŽçã
2-2. codex CLI ã® Parameters éè€ Marshal
pkg/providers/codex_cli_provider.go:154-155
â ããŒã«å®çŸ©ã¯ã«ãŒãå€ã§1å Marshal ããŠãã£ãã·ã¥ããŸãã¯ã«ãŒãå
ã§ json.RawMessage çŽæ¥æžã蟌ã¿ã
â» claude_cli_provider.go:133 ã® string(paramsJSON) â sb.Write(paramsJSON) 㯠byte/string 倿ã®åé¡ã§ãã Phase 0-3 ã§å¯Ÿå¿æžã¿ã
ã³ããã: 2-1, 2-2 ãåå¥ã
ãã§ãŒãº 3: MemoryStore ã®èªã¿åãæé©å (èšèš D-1, D-6)
ç®ç: GetMemoryContext() 1åã§ ReadLongTerm() ã 5å以äžåŒã°ããåé¡ãè§£æ¶ã
3-1. GetMemoryContext() ã content ãã¹ã¹ã«ãŒæ¹åŒã«ãªãã¡ã¯ã¿
æ¢åã® GetMemoryContext() ãçŽæ¥æžãçŽãïŒæ°é¢æ°ã¯è¿œå ããªãïŒã
å
éšã§ ReadLongTerm() ã1åã ãåŒã³ãååŸãã content ãæ¢åã® private ãã«ããŒçŸ€ã«æž¡ãã
HasActivePlan, GetPlanStatus, GetCurrentPhase, GetTotalPhases ã¯ããããããã±ãŒãžå€æ° regex (reActivePlan, reStatus, rePhase, rePhaseHeader) ã1ã2è¡ã§åŒã¶ã ããªã®ã§ãprivate 颿°ãæ°èŠäœæããã€ã³ã©ã€ã³åã§ãããGetPlanPhases ã®ã¿42è¡ã®è€éãªããžãã¯ããããã private variant (getPlanPhasesFrom(content)) ã1ã€è¿œå ã
ä¿®æ£å¯Ÿè±¡ã¯3颿°:
GetMemoryContext() L725 â HasActivePlan/GetPlanStatus ãã€ã³ã©ã€ã³å (GetPlanPhases ã¯äœ¿ããªã):
content := ms.ReadLongTerm()
if reActivePlan.MatchString(content) {
var status string
if m := reStatus.FindStringSubmatch(content); len(m) >= 2 {
status = strings.TrimSpace(m[1])
}
switch status { ... }
}
FormatPlanDisplay() L656 â å
šã¡ãœãããã€ã³ã©ã€ã³å + getPlanPhasesFrom:
content := ms.ReadLongTerm()
if !reActivePlan.MatchString(content) { return "No active plan." }
var status string
if m := reStatus.FindStringSubmatch(content); len(m) >= 2 { status = strings.TrimSpace(m[1]) }
var currentPhase int
if m := rePhase.FindStringSubmatch(content); len(m) >= 2 { currentPhase, _ = strconv.Atoi(m[1]) }
phases := getPlanPhasesFrom(content) // private 颿° (1ã€ã ãæ°èš)
GetPlanContext() L560 â GetCurrentPhase/GetTotalPhases ãã€ã³ã©ã€ã³å:
content := ms.ReadLongTerm()
var currentPhase int
if m := rePhase.FindStringSubmatch(content); len(m) >= 2 { currentPhase, _ = strconv.Atoi(m[1]) }
// GetTotalPhases: rePhaseHeader.FindAllStringSubmatch(content, -1) â max loop
æ¢åã® public ã¡ãœãã (HasActivePlan(), GetPlanStatus() ç) ã¯äºææ§ã®ããæ®ãïŒåäœãã¹ãã»CLI ããåå¥ã«åŒã°ããïŒã
3-2. Split éè€ã®çµ±å (èªã¿åããã¹ã®ã¿)
content ãã¹ã¹ã«ãŒã§è§£æ¶ãããã®ã¯ ReadLongTerm() ã®å€éåŒã³åºãã®ã¿ã
extractPhaseContent(), GetPlanPhases() çãåå¥ã« strings.Split ããåé¡ã¯æ®ãã
察å¿: GetPlanContext() / FormatPlanDisplay() å
ã§1å strings.Split(content, "\n") ãã[]string (è¡ã¹ã©ã€ã¹) ãåãåãå
éšãã«ããŒã远å ãæ¢åã® content string ãåãåããã«ããŒã¯äºææ§ã®ããæ®ãã(GetMemoryContext() èªèº«ã¯ Split ãã«ããŒãçŽæ¥åŒã°ãªããã察象å€ãSplit ã¯åŒã³å
ã® GetPlanContext() çã§çºçããã)
广ç¯å²ã®éå®: ãã®çµ±åãå¹ãã®ã¯èªã¿åãå°çšã¡ãœãã (GetPlanContext, FormatPlanDisplay) ã®ã¿ããã¥ãŒããŒã·ã§ã³ç³» (MarkStep, AddStep) 㯠GetMemoryContext() ãçµç±ããçŽæ¥ ReadLongTerm() + Split + WriteLongTerm() ãå®è¡ããããããã® Phase ã§ã¯å¯Ÿè±¡å€ããã¥ãŒããŒã·ã§ã³ç³»ã® Split çµ±åã«ã¯ ParsedPlan ã€ã³ã¡ã¢ãªã¢ãã« (Phase 5) ãå¿
èŠã
ã³ããã: 1ã€ã
ãã§ãŒãº 4: ã¹ãã¬ãŒãžä¿è· â write-behind (èšèšã»ã¯ã·ã§ã³)
ç®ç: microSD æžã蟌ã¿åæ°ã 97% åæžã
4-1. stats.json ã® write-behind
RecordUsage()L77 /RecordPrompt()L90 ã®t.save()åŒã³åºããåé€ (ã«ãŠã³ã¿æŽæ°ã¯ã€ã³ã¡ã¢ãªã®ã¿ã«)- èµ·åæã«
time.NewTicker(5 * time.Minute)ât.save()ã®ã¿ã€ã㌠goroutine 1æ¬è¿œå Close()ã¡ãœãããæ°èŠè¿œå : ã¿ã€ããŒåæ¢ + æçµt.save()Reset()L111 ã®t.save()ã¯æå³çãã§ãã¯ãã€ã³ããªã®ã§å³æç¶æ- dirty ãã©ã°ã¯äžèŠ (ã¿ã€ããŒãç¡æŽæ°æã« save() ããŠãåå 容ã®äžæžãã§ç¡å®³)
4-2. sessions/*.json ã® write-behind
AddFullMessage() ã¯ãã§ã«ã€ã³ã¡ã¢ãªã®ã¿ã®æäœãæžã蟌ã¿ã¯ loop.go ã Save() ãæç€ºçã«åŒã¶5ç®æã§çºçããã
| loop.go è¡ | æè | é »åºŠ | æ¹é |
|---|---|---|---|
| L996 | ãšãŒãžã§ã³ãã¿ãŒã³çµäº | æ¯ã¿ãŒã³ | dirty ããŒã¯å (äž»èŠã¿ãŒã²ãã) |
| L299 | /plan start clear å±¥æŽã¯ãªã¢ |
äœé »åºŠ | 峿æžã蟌ã¿ç¶æ (æå³çãã§ãã¯ãã€ã³ã) |
| L836 | tool call sanitize | äœé »åºŠ | 峿æžã蟌ã¿ç¶æ |
| L2339 | 匷å¶å±¥æŽå§çž® | äœé »åºŠ | 峿æžã蟌ã¿ç¶æ |
| L2568 | ãµããªãŒçæã»ãã©ã³ã±ãŒã | äœé »åºŠ | 峿æžã蟌ã¿ç¶æ |
- L996 ã®
Save()ãMarkDirty()ã«å€æŽãããã¯ã°ã©ãŠã³ããã©ãã·ã£ãŒ (5åã¿ã€ããŒ) ã§é å»¶æžã蟌㿠- æ®ã4ç®æã¯æå³çãªãã§ãã¯ãã€ã³ããªã®ã§
Save()ãå³æç¶æ SessionManagerã«dirtyKeys map[string]bool+ ãã©ãã·ã£ãŒ goroutine 远å- ã·ã£ããããŠã³ããã¯ã§å š dirty ã»ãã·ã§ã³ããã©ãã·ã¥
AppendToday() ã«ã€ããŠ
AppendToday() (æ¥æ¬¡ããŒã远èš) 㯠write-behind ã®å¯Ÿè±¡å€ãšãããçç±:
- æžã蟌ã¿é »åºŠãäœãïŒæ¥æ¬¡ããŒãè¿œèšæã®ã¿ïŒ
- æžã蟌ã¿å 容ããŠãŒã¶ãŒã®æå確èªå¯Ÿè±¡ã§ãããå³æåæ ãæãŸãã
- sessions/stats ãšç°ãªããé å»¶ã®ã¡ãªãããå°ãã
ã³ããã: 4-1, 4-2 ãåå¥ã
ãã§ãŒãº 5: çºå±çæé©å (ä»»æ)
å®è£ ã³ã¹ããé«ã or 广ãéå®çãªãã®ãå¿ èŠã«å¿ããŠçæã
| é ç® | å 容 | èŠéãçç± |
|---|---|---|
| F: sync.Pool | web.go extractText, telegram.go Markdown 倿 | åŒã³åºãé »åºŠãäœã Pool ã®å¹æãèãå¯èœæ§ |
| G: LRU O(1) å | search_cache.go ã doubly-linked list ã« | maxEntries=100 ã§ O(n) ã§ãååé«é |
| D-2: FunctionCall.Arguments åå€æŽ | string â json.RawMessage |
å šãããã€ããŒã«æ³¢åãç Žå£çå€æŽ |
| D-3: Parameters ã RawMessage ã« | åäž | åäž |
| D-5: Session.Messages ã immutable ã« | COW or linked list | ã»ãã·ã§ã³ç®¡çã®æ ¹æ¬åèšèšãå¿ èŠ |
| ParsedPlan ã€ã³ã¡ã¢ãªã¢ãã« | MemoryStore ã«ããŒã¹æžã¿æ§é äœãåžžé§ãã MarkStep/AddStep ã® Split éè€ãæ ¹æ¬è§£æ¶ (D-1/D-6 å®å šè§£æ±º) | èšèšå€æŽãåºç¯å² |
ã»ãã·ã§ã³ç®¡çã®çºå±ç屿
çŸç¶ã®èšèšã¯ãæ£ç¢ºæ§ãã¯æçããŠããããã©ã€ããµã€ã¯ã«ããæ¬ èœããŠããã 以äžã¯å®è£ ã³ã¹ãã»å®çšäŸ¡å€ã®èгç¹ã§3段éã«æŽçããå°æ¥ã®çºå±æ¹åã
è¿æ: éçšäžã®æç
ã»ãã·ã§ã³ã©ã€ããµã€ã¯ã«ã®æç€ºå
çŸåš Session.Created / Session.Updated ã¯ãã£ãŒã«ãã«ååšããã䜿ãããŠããªããDelete() API ãš TTL ä»ããšãã¯ã·ã§ã³ãå ããã ãã§ãã»ãã·ã§ã³ãæèçã«ç®¡çã§ãããç¶æ
ã«ãªãã
type Session struct {
// æ¢åãã£ãŒã«ã ...
Name string // ãproject-xããªã©ã®ååä»ã
Tags []string // ã¿ã°ã«ããåé¡
Archived bool // ã¢ãŒã«ã€ãæžã¿ãã©ã°
ParentKey string // 芪ã»ãã·ã§ã³ (subagent chain ã®æç€ºå)
}
SessionManager.Delete(key)ã®è¿œåsessionLocks sync.Map(loop.go) ã® GC â çŸç¶ãŠããŒã¯ããŒãå¢ãããšæªååã§èšãã- èµ·åæã®
loadSessions()ãé å»¶ããŒãå (ã»ãã·ã§ã³ãã¡ã€ã«æ°ãå¢ããå Žåã®èµ·åæé察ç)
äžæ: äŒè©±ã®æ§é å
ãã§ãã¯ãã€ã³ã / ããŒã«ããã¯
[turn 1] â [turn 2] â [turn 3 : checkpoint A] â [turn 4] â [turn 5]
â
ãturn 3 ã«æ»ãã= turn 4, 5 ãæšãŠãŠåé
LLM ãæ¹åãééããæç¹ã«æ»ããŠãŒã¹ã±ãŒã¹ã¯å人å©çšã§ãé »ç¹ã«çºçããã
Session.Messages ã append-only immutable ã«ãã (D-5 COW) ãšèªç¶ã«ã€ãªããã
ååä»ãã»ãã·ã§ã³ / æå³çãªåãæ¿ã
çŸåšã»ãã·ã§ã³ããŒã¯ãã©ãããæ¥ããã(ãã£ã³ãã«+ãã¢) ã§æ±ºãŸããããããäœã®æèããã§ãåãæ¿ããããããã«ãã:
/new-session "refactoring-auth" â æ°ããã»ãã·ã§ã³ãæç€ºçã«éå§
/switch-session "refactoring-auth" â éå»ã®ååä»ãã»ãã·ã§ã³ã«æ»ã
/list-sessions â ã»ãã·ã§ã³äžèЧ
routing/session_key.go ã® BuildAgentPeerSessionKey() ã¯ãã§ã«æè»ãªæ§é ãªã®ã§ãã»ãã·ã§ã³åã key ã®äžéšãšããŠæã€ããšã¯èšèšäžç¡çããªãã
é·æ: ã»ãã·ã§ã³éã®é¢ä¿
ãµããšãŒãžã§ã³ãã»ãã·ã§ã³ã®ã°ã©ãå
çŸåšããµããšãŒãžã§ã³ãã»ãã·ã§ã³ã¯ IsSubagentSessionKey() ã§å€å®ã§ãããããã©ã®èŠªã»ãã·ã§ã³ããçãŸãããããšãã芪åé¢ä¿ã¯ key ã®åœåèŠåã«æé»çã«åã蟌ãŸããŠããã ã:
agent:main:main
ââ subagent:abc123:main â 芪ã main:main ãšã¯æ§é çã«ç®¡çãããŠããªã
ââ subagent:def456:main
Session.ParentKey ã远å ããŠã»ãã·ã§ã³ãã°ã©ããšããŠæãŠããšãããã®ãµããšãŒãžã§ã³ããäœããã£ãããã芪ã»ãã·ã§ã³ããé¡ããããã«ãªãã
ã¯ãã¹ã»ãã·ã§ã³æ€çŽ¢
ã以å auth ã«ã€ããŠè©±ãããšãäœã決ããã£ãã
â ã»ãã·ã§ã³æšªæã§ããŒã¯ãŒãæ€çŽ¢ â é¢é£ã¿ãŒã³ãæœåºããŠããã³ããã«æ³šå
¥
MEMORY.md ã¯çŸç¶ããã©ã³å°çšã®æ°žç¶ã¡ã¢ãªãã ããã¯ãã¹ã»ãã·ã§ã³æ€çŽ¢ã¯ãã®è£å®ãšããŠæ©èœããã
泚æ: é å»¶ããŒããšã®ç«¶å è¿æã®ãé å»¶ããŒãåããå®è£ ãããšãèµ·åæã«å šã»ãã·ã§ã³ãã¡ã¢ãªã«ããåæã厩ããã äž¡æ¹ãæ¡çšããå Žåã¯ä»¥äžã®ãããããéžæããå¿ èŠããã:
- æ€çŽ¢æãã«ã¹ãã£ã³: æ€çŽ¢ãªã¯ãšã¹ãã®ãã³ã«
sessions/ãã£ã¬ã¯ããªã®å š JSON ãèªã (äœé »åºŠãªã蚱容)- ããã¯ã°ã©ãŠã³ãã€ã³ããã¯ã¹: èµ·ååŸã«ãŽã«ãŒãã³ã§å šãã¡ã€ã«ãéåæã¹ãã£ã³ããããŒã¯ãŒãã€ã³ããã¯ã¹ãæ§ç¯ã»ç¶æãã
èšèšäžã®éžæè¢
çŸåšã®æ§é ã¯2ã€ã®å²åŠã®äžéã«äœçœ®ããŠãã:
| å²åŠA: å±¥æŽäžå¿ | å²åŠB: ç¥èäžå¿ |
|---|---|
Session.Messages ãå¯äžã®çå® |
éèŠãªæ
å ±ã MEMORY.md çã«èžç |
| äŒè©±ããåçãããŠã³ã³ããã¹ãåçŸ | æ§é åç¥èããæ³šå ¥ãããŠã³ã³ããã¹ãæ§ç¯ |
| ããŒã«ããã¯ã»ãã©ã³ããèªç¶ãªæ¡åŒµ | ã¯ãã¹ã»ãã·ã§ã³æ€çŽ¢ãèªç¶ãªæ¡åŒµ |
ãã®ã³ãŒãããŒã¹ã¯ãã§ã« Session.Messages (å²åŠA) ãš MEMORY.md (å²åŠB) ãå
±åããŠãããPhase 5 ã® ParsedPlan ã€ã³ã¡ã¢ãªã¢ãã« ã¯å²åŠB æ¹åãžã®åžç³ã«ãªãã
å®è£ é ãµããªãŒ
Phase 0 âââ Phase 1 âââ Phase 2 âââ Phase 3 âââ Phase 4
æ©æ¢°ç 倿ž¡ã JSON Memory Storage
眮ãæã æé©å ããããã¹ èªã¿åã write-behind
(4 commits) (1 commit) (2 commits) (2 commits) (2 commits)
- Phase 0ã2: ã¢ãã±ãŒã·ã§ã³åæž (GC å§å軜æž)
- Phase 3: syscall + Split/Join åæž (CPU + ã¢ãã±ãŒã·ã§ã³)
- Phase 4: ãã£ã¹ã¯æžã蟌ã¿åæž (microSD 寿åœä¿è·)
- Phase 5: å¿ èŠã«å¿ããŠåå¥å€æ