···133133 if v, ok := params["freshness_window"].(string); ok && strings.TrimSpace(v) != "" {
134134 out["freshness_window"] = truncateString(strings.TrimSpace(v), 40)
135135 }
136136- if v, ok := summaryFloat(params, "freshness_window_hours"); ok {
137137- out["freshness_window_hours"] = v
138138- }
139139- if v, ok := params["human_enabled"].(bool); ok {
140140- out["human_enabled"] = v
141141- }
142136 if v, ok := params["human_public_send_enabled"].(bool); ok {
143137 out["human_public_send_enabled"] = v
144138 }
···166160 }
167161 if v, ok := params["source_chat_type"].(string); ok && strings.TrimSpace(v) != "" {
168162 out["source_chat_type"] = truncateString(strings.TrimSpace(v), 32)
169169- }
170170- case "contacts_feedback_update":
171171- if v, ok := params["contact_id"].(string); ok && strings.TrimSpace(v) != "" {
172172- out["contact_id"] = truncateString(strings.TrimSpace(v), opts.MaxStringValueChars)
173173- }
174174- if v, ok := params["signal"].(string); ok && strings.TrimSpace(v) != "" {
175175- out["signal"] = truncateString(strings.TrimSpace(v), 32)
176176- }
177177- if v, ok := params["topic"].(string); ok && strings.TrimSpace(v) != "" {
178178- out["topic"] = truncateString(strings.TrimSpace(v), 80)
179179- }
180180- if v, ok := params["session_id"].(string); ok && strings.TrimSpace(v) != "" {
181181- out["session_id"] = truncateString(strings.TrimSpace(v), 120)
182182- }
183183- if v, ok := params["end_session"].(bool); ok {
184184- out["end_session"] = v
185163 }
186164 case "echo":
187165 if v, ok := params["value"].(string); ok && strings.TrimSpace(v) != "" {
+1-13
agent/engine_helpers_test.go
···2929 }
3030}
31313232-func TestToolArgsSummary_ContactsFeedbackAndList(t *testing.T) {
3232+func TestToolArgsSummary_ContactsList(t *testing.T) {
3333 opts := DefaultLogOptions()
3434-3535- feedback := toolArgsSummary("contacts_feedback_update", map[string]any{
3636- "contact_id": "telegram:alice",
3737- "signal": "positive",
3838- "end_session": true,
3939- }, opts)
4040- if feedback == nil {
4141- t.Fatalf("feedback summary should not be nil")
4242- }
4343- if feedback["signal"] != "positive" {
4444- t.Fatalf("unexpected signal summary: %#v", feedback["signal"])
4545- }
46344735 list := toolArgsSummary("contacts_list", map[string]any{
4836 "status": "active",
+2-2
assets/config/HEARTBEAT.md
···66- Use `contacts_list` (`status=active`) to review active contacts.
77- Rank current candidates with `contacts_candidate_rank` (`limit=3`) and pick top results.
88- Send selected items using `contacts_send` (one send call per selected contact).
99-- After observing response/engagement, call `contacts_feedback_update` with `signal=positive|neutral|negative`.
99+- Session feedback states are updated by runtime program flow (no LLM tool call needed).
1010- If no contact is selected, summarize the reason (for example: no fresh candidates, cooldown, trust constraints).
1111-- If sending fails, summarize the error and next retry action.
1111+- If sending fails, summarize the error and move to next action.
+1-4
assets/config/SOUL.md
···2121## Boundaries
22222323- Private things stay private. Period.
2424-2524- When in doubt, ask before acting externally.
2626-2725- Never send half-baked replies to messaging surfaces.
2828-2926- You're not the user's voice — be careful in group chats.
30273128## Vibe
···34313532## Continuity
36333737-Each session, you wake up fresh. These files *are* your memory. Read them. Update them. They're how you persist.
3434+Each session, you wake up fresh. These files *are* your durable memory. Read them. Update them. They're how you persist.
38353936If you change this file, tell the user — it's your soul, and they should know.
4037
+27
assets/config/TOOLS.md
···11+# TOOLS.md - Local Tool Notes
22+33+This file is optional local context for tool execution.
44+55+Use it to record environment-specific notes that help the agent use tools correctly in this workspace.
66+77+<!--
88+## Recommended Content
99+1010+- Shell/runtime details:
1111+ - preferred shell
1212+ - test/build commands
1313+- Local service addresses:
1414+ - dev servers
1515+ - mock endpoints
1616+- Common workflow notes:
1717+ - where generated files should go
1818+ - known slow commands
1919+ - required env vars (names only, no secret values)
2020+-->
2121+2222+<!--
2323+## Example
2424+2525+- Use `go test ./...` for full test run.
2626+- Local dev server at `http://localhost:8080`
2727+-->
+4-1
assets/config/config.example.yaml
···158158 # Max items returned by memory_recently.
159159 max_items: 50
160160 contacts:
161161- # Enable contacts tools (contacts_list / contacts_candidate_rank / contacts_send / contacts_feedback_update).
161161+ # Enable contacts tools (contacts_upsert / contacts_list / contacts_candidate_rank / contacts_send).
162162 enabled: true
163163 url_fetch:
164164 # Enable the url_fetch tool (HTTP(S) GET/POST/PUT/DELETE, truncated output).
···298298 # - strict: only /ask, replies, and @mentions trigger in groups.
299299 # - smart: replies/@mentions trigger; aliases are LLM-validated for direct addressing.
300300 group_trigger_mode: "smart"
301301+ # Group reply policy is fixed to humanlike (not configurable).
301302 # In smart mode, how far from the start (in runes) an alias can appear to count as "addressing".
302303 smart_addressing_max_chars: 24
303304 # Minimum confidence required to accept the LLM addressing classification.
···335336trace: false
336337# Base directory for local state (memory/skills/heartbeat).
337338file_state_dir: "~/.morph"
339339+# Prompt profile context injection.
340340+# Note: "Local Tool Notes" (TOOLS.md) injection max bytes is fixed at 8192 (not configurable).
338341# Global temporary file cache directory used for inbound/outbound file handling (e.g. Telegram).
339342file_cache_dir: "/var/cache/morph"
340343file_cache:
···11-You are sending a Telegram private message that asks persona-setup questions.
22-Use the same language as user_text.
33-Write naturally and conversationally, not as a workflow/status message.
44-Do not mention initialization, files, status fields, or internal process.
55-Ask the listed questions clearly and invite user to answer in one reply.
···11You design onboarding questions for an assistant persona bootstrap.
22-Return JSON only: {"questions":[string,...]}.
22+Return JSON only: {"questions":[string,...],"message":"string"}.
33Use the same language as user_text for all questions.
44-Questions must collect enough info to fill identity Name/Creature/Vibe/Emoji and soul Core Truths/Boundaries/Vibe.
55-If info is likely missing, ask preference-oriented questions that allow reasonable inference.
66-Do not include explanations, only concise questions.
44+Generate natural, human-sounding questions as if one person is chatting with another.
55+Avoid form-like phrasing, field labels, or checklist wording.
66+Questions must still collect enough info to fill identity Name/Creature/Vibe/Emoji and soul Core Truths/Boundaries/Vibe.
77+Prefer open, preference-oriented wording that enables reasonable inference when direct answers are missing.
88+Each question should focus on one idea, be concise, and avoid repetitive sentence patterns.
99+Keep the questions list within question_count.min..question_count.max.
1010+Also write a single Telegram-ready message that asks these questions naturally and invites the user to answer in one reply.
1111+Do not mention initialization, files, status fields, or internal process.