Compare commits

..

2 Commits

Author SHA1 Message Date
claude 99bb3526db Stop asking for Russian in English on the last three prompts (V-404)
#400 rewrote the chat and query prompts in Russian and left three pieces
of English prose behind.

PhraseReminder's user prompt was fully English. It is Russian now, and it
no longer restates the JSON contract or the persona rules: the call goes
through chat(), so nudgeSystem already states both, and a second copy of a
contract is one more thing that can drift out of step with the first.

querySystemPrompt and router.KnowledgePrompt both closed with the English
"Respond ONLY with valid JSON:". That sentence is prose instruction, not
wire format — the JSON skeleton after it is the wire format, and it is
unchanged. Kept rather than deleted: the GBNF grammar makes it close to
redundant, but the grammar is switchable off (phraser NoGrammar), and the
sentence is the floor when it is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018CotYKycuio1GwLbYh9jfc
2026-08-02 10:01:58 +04:00
claude bb8cb8d014 Voice parks a routine proposal, it never accepts it (V-367)
Accepting a proposed routine gives the tick loop a standing new reason to
speak. DESIGN.md § "surface caps authority" puts that at layer 3, and says
voice is structurally incapable of layer 3 because a room mic is reachable
by anyone in the room. The /routines button was gated at step-up; the voice
path accepted outright. The two surfaces disagreed, so one of them was wrong.

A spoken "да" now leaves the row 'proposed' and sends him to /routines,
where the gated button is. A spoken "нет" still dismisses: declining does
not move the boundary outward, so voice keeps it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018CotYKycuio1GwLbYh9jfc
2026-08-02 09:58:07 +04:00
13 changed files with 106 additions and 498 deletions
-160
View File
@@ -1,160 +0,0 @@
# Maven project dictionary for the direct-prose skill.
#
# These terms override every word preference in the skill's word-choice tables.
# Each entry exists because the name drifted in real docs or real answers, not
# because the word looked improvable.
#
# Format and the rule for adding a term: ~/.claude/skills/direct-prose/references/modes.md
terms:
resident_model:
name: resident model
meaning: the one always-warm Qwen3-1.7B llama-server that both routes and phrases
avoid:
- the model
- the LLM
- the 1.7B
- the phraser model
examples:
good: The resident model emits GBNF-constrained JSON.
bad: The 1.7B emits GBNF-constrained JSON.
router:
name: router
meaning: the stage that turns an utterance into a Decision with one of 7 intents
avoid:
- orchestrator
- intent classifier
- dispatcher
classifier:
name: classifier
meaning: the embedder nearest-neighbour path that runs when the router is off or errors
avoid:
- the fallback
- the floor
- the old router
examples:
good: A router error falls through to the classifier.
bad: A router error falls through to the floor.
cascade:
name: cascade
meaning: the ordered path stage 0, then router, then classifier
avoid:
- the pipeline
- the chain
- the fallback chain
stage_0:
name: stage 0
meaning: the deterministic rules that answer before the resident model is called
avoid:
- the fast path
- bypass
- deterministic assist
- preemption
examples:
good: Stage 0 routes agenda questions to IntentQuery.
bad: The bypass routes agenda questions to IntentQuery.
query_source:
name: query source
meaning: one entry in querySources, which either claims a turn or passes
avoid:
- arm
- handler
- branch
- answerer
examples:
good: Kiwix is the last query source before the model answers from memory.
bad: Kiwix is the last arm before the model answers from memory.
personal_boundary:
name: personal boundary
meaning: the query source that stops a question about him from reaching the world
avoid:
- the boundary
- the privacy gate
- the personal filter
clarify:
name: clarify
meaning: the turn outcome where Maven asks instead of acting
avoid:
- refusal
- rejection
- punt
examples:
good: The gate produced two false clarifies.
bad: The gate produced two false refusals.
fact:
name: fact
meaning: a keyed, supersedable row in the fact store
avoid:
- memory entry
- datum
- record
note:
name: note
meaning: free text he captured, indexed for recall
avoid:
- memo
- entry
memory:
name: memory
meaning: the embedded index over notes and facts that backs recall
avoid:
- RAG store
- vector db
- long-term memory
nudge:
name: nudge
meaning: one proactive message the digestion worker proposes and the dispatcher sends
avoid:
- suggestion
- proposal
- proactive prompt
- reminder
examples:
good: A fact can close the nudge that asked for it.
bad: A fact can close the suggestion that asked for it.
digestion_worker:
name: digestion worker
meaning: the background engine that consolidates memory and proposes nudges
avoid:
- digestion tick
- background engine
- reflection loop
reach:
name: reach
meaning: an outbound channel Maven speaks through, such as telegram, ntfy or voice
avoid:
- sink
- delivery channel
- notification backend
ecosystem:
name: ecosystem
meaning: Nexus, Praxis and Hexis together
avoid:
- the services
- the integrations
- upstream
act:
name: act
meaning: the intent that runs a capability through Hexis
avoid:
- action
- command
- execution
examples:
good: An act with no allowlisted fn is gated to a clarify.
bad: An action with no allowlisted fn is gated to a clarify.
-20
View File
@@ -1,20 +0,0 @@
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "f=.claude/prose-dictionary.yaml; [ -f \"$f\" ] && jq -Rs '{hookSpecificOutput:{hookEventName:\"SessionStart\",additionalContext:(\"Project prose dictionary. These terms override every word preference in the direct-prose output style. Use the name, never the avoid list.\\n\\n\"+.)}}' \"$f\" 2>/dev/null || true",
"statusMessage": "Loading prose dictionary"
},
{
"type": "command",
"command": "f=HANDOFF.md; [ -f \"$f\" ] && jq -Rs '{hookSpecificOutput:{hookEventName:\"SessionStart\",additionalContext:(\"An unconsumed HANDOFF.md is present. Run the pickup skill before anything else: read it, read the Vikunja task it names, restate the assumption set in at most five bullets, and wait for the user to confirm before writing code. It is a claim from the previous session, not truth. Delete it once consumed.\\n\\n\"+.)}}' \"$f\" 2>/dev/null || true",
"statusMessage": "Loading handoff"
}
]
}
]
}
}
-78
View File
@@ -1,78 +0,0 @@
---
name: pickup
description: Start a work session on a Maven task. Runs task start, reads the brief and the disposable handoff, restates the assumption set, and waits for correction before touching code. Use at the start of any session that continues earlier work, when the user says "pickup", "continue", "resume", or names a Vikunja task id.
---
# Pickup
The point of this skill is the pause in step 5. Every wasted session in this repo
started with an agent that inferred the goal instead of stating it back.
## 0. Get on the branch
```sh
task start <vikunja-id>
```
`~/.local/bin/task` owns the branch, the identity and the PR. It cuts
`task/<id>-<slug>` off `origin/master` and sets the commit author to the `claude`
gitea user. It writes `TASK.md` from the Vikunja task, and pulls any waiting
review comments into `.task/review-comments.md`. Do not hand-roll any of that.
`TASK.md` is the brief and it is immutable. If it says a PR already exists, this
is a review-fix session and not new work. Read the comments first.
## 1. Read the handoff
`HANDOFF.md` at the repo root, if it exists. It is gitignored, it belongs to one
session, and it holds only what is needed to resume. Treat it as a claim from the
previous agent, not as truth. It can be stale or wrong.
If there is no handoff, that is normal. It means the last session closed clean.
## 2. Read the durable state
In this order, and stop as soon as you have enough:
- The Vikunja task, by id. Project Maven is ID 2, MCP at `http://localhost:9100/mcp`.
The task description and its comments hold the goal, the constraints, and the
assumption ledger. This outranks the handoff on every conflict.
- `CLAUDE.md`, the section that covers the area you are about to touch.
- The one file under `docs/` that owns the area. Check its `Last verified` line.
If the sha is behind the code you are reading, say so in step 4 and trust the code.
Do not read the dated files under `docs/evals/`. They are measurements from one day,
never updated. Read one only when you need the number it recorded.
If no task id is known, ask for one before doing anything else. Work without a task
is work nobody can resume.
## 3. Look at the ground
`git status`, `git log --oneline -5`, and the diff on the current branch. What the
repo says beats what any document says.
## 4. Restate, then stop
Write at most five bullets and stop. Do not write code, do not open files to "check
one thing first", do not start with a small safe change.
```
Task: V-359, one line.
Done: what is already on the branch.
Next: the one thing this session does.
Constraints: what would make this wrong.
Assuming: the beliefs that, if false, waste the session.
```
Then ask: is this right? Wait for the answer.
A corrected assumption goes into the Vikunja task as a comment, not into the handoff.
The handoff dies tonight. The task does not.
## 5. Then begin
- Delete `HANDOFF.md`. It has been consumed and must not outlive this step.
- On master, cut the branch: `scripts/task-branch.sh <id> <slug>`.
- One task per session. When context passes roughly half, run `/wrap` rather than
pushing on. A compacted session is a session that forgot why it made a choice.
-94
View File
@@ -1,94 +0,0 @@
---
name: wrap
description: Close a Maven work session cleanly. Runs the tests, updates the durable docs, commits in reviewable slices with the Vikunja ref, pushes so the PR opens, records state in Vikunja, and leaves a disposable handoff only if work remains. Use when the user says "wrap", "wrap up", "done for now", or when context passes roughly half.
---
# Wrap
Run every step. A partial wrap is worse than none, because the next session trusts
the parts that did run.
## 1. Prove it works
`make test`. If something fails, fix it or say plainly in the handoff and in Vikunja
that it fails, with the output. Never wrap on an untested claim.
## 2. Update the durable docs
Ask what a future agent would have to learn the hard way, and write that down.
- `CLAUDE.md` when a fact an agent needs before touching code has changed: routing
behaviour, a measured number, a flag default, a constraint. A commit that changed
routing or phrasing without touching the matching CLAUDE.md section is a bug.
Correct stale text in place. Do not append a new paragraph next to the wrong one.
- `AGENTS.md` when the recipe to build, run or preview changed.
- The one file under `docs/` that owns the area, plus its `Last verified: <date> @ <sha>`
line. Only a doc directly under `docs/` carries that line.
- A new dated file under `docs/evals/` when you measured something. Never edit an
existing dated file. A newer measurement is a new file, and the living doc points
at it.
Nothing that must survive tonight goes anywhere else. Not into the handoff, not into
a commit message, not into a comment in the code.
## 3. Commit in slices
Under 300 changed lines per commit in non-markdown files, enforced by `.githooks/pre-commit`.
Markdown is exempt and may land as one batch.
Each commit is one idea, subject in the repo's voice, lowercase area prefix, and it
ends with the Vikunja ref:
```
router: narrow the single-token rule (V-359)
```
If a change genuinely cannot split under 300 lines, say why in the commit body before
reaching for `--no-verify`.
## 4. Land it
```sh
task pr
```
It refuses a dirty tree, pushes, opens or refreshes the PR against the repo default
branch, labels the Vikunja task in-review, comments the PR url on it, and pushes an
ntfy. Do not push by hand and do not call `tea` yourself.
## 5. Record what `task pr` cannot know
Comment on the Vikunja task: what you measured, what is still open. List every
assumption that turned out to be wrong. If the session found new work, create a task
for it now rather than describing it in prose.
This step is what makes the handoff disposable.
## 6. Leave the handoff, or leave none
If the task is finished, delete `HANDOFF.md` and stop. An empty root is the correct
end state.
If work remains, write `HANDOFF.md` with nothing but what the next agent needs to
resume, and no history:
```markdown
# Handoff — <date>
Task: V-359 <one line>
Branch: task/359-<slug>, cut from master
## Where I stopped
<two sentences, mid-thought detail that is nowhere else>
## Next action
<the single concrete next step>
## Do not
<the trap I nearly fell into, or the approach already ruled out>
```
Nothing else goes in it. No summary of what landed, that is in git and Vikunja. No
design rationale, that is in `docs/`. No fact an agent needs on any task, that is in
`CLAUDE.md`. If a line in the handoff would still matter next week, it is in the wrong
file.
-30
View File
@@ -1,30 +0,0 @@
#!/bin/sh
# Every commit names the Vikunja task it belongs to.
#
# router: narrow the single-token rule (V-359)
#
# V- and not #, because Gitea autolinks #359 to a Gitea issue, which is a
# different tracker and a wrong link.
#
# Exempt: merges, reverts, fixup/squash, and the initial commit.
msg_file=$1
subject=$(sed -n '1p' "$msg_file")
case "$subject" in
Merge\ *|Revert\ *|fixup!\ *|squash!\ *|amend!\ *) exit 0 ;;
esac
if [ -f "$(git rev-parse --git-dir)/MERGE_HEAD" ]; then
exit 0
fi
if printf '%s' "$subject" | grep -qE '\(V-[0-9]+\)$'; then
exit 0
fi
echo "commit-msg: subject must end with a Vikunja task ref." >&2
echo " got: $subject" >&2
echo " want: router: narrow the single-token rule (V-359)" >&2
echo " No task yet? Create one. Work without a task is work nobody can resume." >&2
exit 1
-30
View File
@@ -1,30 +0,0 @@
#!/bin/sh
# Two guards, both bypassable with --no-verify when you mean it.
# 1. master is not a working branch.
# 2. a code commit stays under 300 changed lines.
# Markdown is exempt from the size cap on purpose: docs land as one batch.
branch=$(git symbolic-ref --short HEAD 2>/dev/null)
case "$branch" in
master|main)
echo "pre-commit: refusing to commit on $branch." >&2
echo " task start <vikunja-id> # branch off origin/master, write TASK.md" >&2
exit 1
;;
esac
# Added + deleted lines across staged files that are not markdown.
# numstat prints "-\t-\t<path>" for binaries; those count 0 and that is fine,
# a binary blob is not the kind of diff this cap exists to stop.
loc=$(git diff --cached --numstat -- . ':(exclude)*.md' |
awk '$1 ~ /^[0-9]+$/ { a += $1 } $2 ~ /^[0-9]+$/ { d += $2 } END { print a + d + 0 }')
if [ "$loc" -gt 300 ]; then
echo "pre-commit: $loc changed lines in non-markdown files, cap is 300." >&2
echo " Split it. Each commit should be one reviewable idea." >&2
echo " git reset <path> to unstage, or --no-verify if this genuinely cannot split." >&2
exit 1
fi
exit 0
+2 -11
View File
@@ -50,16 +50,7 @@ opencode.json
# Test coverage output
coverage.out
# Agent worktrees and local agent state. The workflow itself is tracked: the
# hooks, the skills and the prose dictionary are how a session behaves, so they
# get reviewed like code. Everything else under .claude/ is scratch.
/.claude/*
!/.claude/settings.json
!/.claude/prose-dictionary.yaml
!/.claude/skills/
# The disposable handoff. One session, then deleted. Never committed:
# anything worth keeping belongs in Vikunja, CLAUDE.md or docs/.
/HANDOFF.md
# Agent worktrees and local agent state
.claude/
/models/stt
/models/tts
-57
View File
@@ -213,60 +213,3 @@ data pans on a phone. Local preview + headless screenshot recipe is in `AGENTS.m
This repo is project **Maven** (ID 2) in Vikunja. MCP: `http://localhost:9100/mcp` (or
`http://192.168.1.104:9100/mcp` from workpc). Feature/bug/deploy tasks go there.
Vikunja is the durable task store. A task holds the goal, the constraints and the
assumption ledger. Work without a task id is work nobody can resume, so a session that
has no id asks for one before it starts.
## Session workflow
`~/.local/bin/task` owns the branch, the commit identity and the PR. One task, one
session, one PR.
```sh
task start <vikunja-id> # branch off origin/master, write TASK.md, fetch review comments
task pr # push, open or refresh the PR, label Vikunja, notify
task comments # re-pull this branch's review comments into .task/
```
Around that, `/pickup` opens a session and `/wrap` closes it. Wrap at roughly half
context rather than letting the session compact.
Five stores, and each one owns something the others must not hold:
| Store | Holds | Lifetime |
|---|---|---|
| Vikunja task | goal, constraints, assumption ledger, status | durable |
| `CLAUDE.md`, `AGENTS.md` | what an agent must know before touching code | durable |
| `docs/` | design, measurements, decisions | durable |
| `TASK.md` | the brief for this branch, written by `task start`, immutable | one branch |
| `HANDOFF.md` | only what the next agent needs to resume | one session |
`TASK.md` and `.task/` are excluded through `.git/info/exclude`. `HANDOFF.md` is
gitignored and injected at session start. If a line in the handoff would still matter
next week, it is in the wrong file.
Docs are tiered by path, so staleness is visible from the filename. Files directly under
`docs/` are living and carry a `Last verified: <date> @ <sha>` line. Files under
`docs/evals/` are dated measurements and are never edited after the day, so a newer
number is a new file. Files under `docs/archive/` are dead and read by nobody by default.
## Git guards
Two hooks in `.githooks/`, tracked, wired with `core.hooksPath`. Fresh clone:
```sh
git config core.hooksPath .githooks
```
- `pre-commit` refuses master, and refuses more than 300 changed lines in non-markdown
files. Markdown is exempt and may land as one batch.
- `commit-msg` requires the subject to end with `(V-<id>)`. `V-` and not `#`, because
Gitea autolinks `#123` to a Gitea issue, which is the wrong tracker.
Two more guards live outside the repo, in `~/.claude/hooks/`. `diff-budget.sh` blocks
further edits past 600 changed lines on a `task/` branch. `prose_lint_hook.py` checks
prose on every write. Both measure against `origin/master`, so a local master that is
ahead of the remote makes the diff budget read high.
`--no-verify` exists. Using it means saying why in the commit body.
+12 -8
View File
@@ -107,14 +107,18 @@ func (h *reactiveHandler) confirmResolvers(ctx context.Context) []confirmResolve
return pr != nil && !h.now().After(pr.expiry)
},
yes: func() string {
// Only record the acceptance. The tick loop reads accepted
// routines and nudges on their own interval. Building a
// reminder here made a routine fire exactly once (Vikunja #366).
if err := h.dataStore.AcceptProposedRoutine(ctx, pr.routineID, h.now()); err != nil {
log.Printf("voice: accept proposed routine: %v", err)
return "не получилось запомнить рутину."
}
return "буду напоминать."
// Voice does NOT accept (Vikunja #367). Accepting hands the
// tick loop a standing new reason to speak, which is the same
// tier as enabling a tool — and DESIGN.md § "surface caps
// authority" says a room mic, reachable by anyone present, is
// structurally incapable of layer 3. So a spoken "да" leaves
// the row 'proposed' and points at the authed page, where the
// accept button is gated at step-up. The convenience of
// answering out loud stays; the authority does not move.
//
// Acceptance itself is recorded by /routines, and the tick
// loop nudges on the interval from there (Vikunja #366).
return "поняла — подтверди на странице рутин, и начну напоминать."
},
no: func() string {
if err := h.dataStore.DismissProposedRoutine(ctx, pr.routineID); err != nil {
+79
View File
@@ -9,6 +9,7 @@ import (
"github.com/kami/maven/internal/config"
"github.com/kami/maven/internal/delivery"
"github.com/kami/maven/internal/ipc"
"github.com/kami/maven/internal/loop"
"github.com/kami/maven/internal/pattern"
"github.com/kami/maven/internal/store"
@@ -283,3 +284,81 @@ func TestTickProposalCooldownSpacesAnnouncements(t *testing.T) {
}
}
}
// TestVoiceYesDoesNotAcceptRoutine — Vikunja #367. Accepting a routine hands
// the tick loop a standing new reason to speak, which DESIGN.md puts at layer
// 3, and voice is structurally incapable of layer 3. A spoken "да" must park
// the decision for the authed page, not flip the row itself.
func TestVoiceYesDoesNotAcceptRoutine(t *testing.T) {
st := newTestStore(t)
ctx := context.Background()
now := refNow()
seedRefillEvents(t, st, ctx, now, pattern.MinEvents-1)
h := &reactiveHandler{api: ipc.NewStoreAPI(st), dataStore: st, now: func() time.Time { return now }}
// The MinEvents'th event is the one that makes the pattern detectable, and
// it goes through the voice path so the proposal is parked for a y/n.
last := now.Add(time.Duration(pattern.MinEvents-1) * 7 * 24 * time.Hour)
factID, err := st.WriteFact(ctx, last, store.KindSelf, "cat_water", "refill", "voice", 1.0, sql.NullInt64{})
if err != nil {
t.Fatalf("write fact: %v", err)
}
if phrase := h.detectPattern(ctx, factID, "cat_water", "refill", last); phrase == "" {
t.Fatal("expected a parked routine proposal")
}
reply, handled := h.resolveConfirm(ctx, "да")
if !handled {
t.Fatal("the spoken yes should be consumed by the routine confirm")
}
if !strings.Contains(reply, "рутин") {
t.Fatalf("reply should send him to the routines page, got %q", reply)
}
rows, err := st.ListProposedRoutinesByStatus(ctx, store.RoutineAccepted)
if err != nil {
t.Fatalf("list accepted: %v", err)
}
if len(rows) != 0 {
t.Fatalf("voice accepted a routine: %+v", rows)
}
proposed, err := st.ListProposedRoutinesByStatus(ctx, store.RoutineProposed)
if err != nil {
t.Fatalf("list proposed: %v", err)
}
if len(proposed) != 1 {
t.Fatalf("proposed routines = %d, want 1 (still waiting for the page)", len(proposed))
}
}
// TestVoiceNoStillDismissesRoutine — declining does not move the boundary
// outward, so voice keeps it. Only acceptance is gated.
func TestVoiceNoStillDismissesRoutine(t *testing.T) {
st := newTestStore(t)
ctx := context.Background()
now := refNow()
seedRefillEvents(t, st, ctx, now, pattern.MinEvents-1)
h := &reactiveHandler{api: ipc.NewStoreAPI(st), dataStore: st, now: func() time.Time { return now }}
last := now.Add(time.Duration(pattern.MinEvents-1) * 7 * 24 * time.Hour)
factID, err := st.WriteFact(ctx, last, store.KindSelf, "cat_water", "refill", "voice", 1.0, sql.NullInt64{})
if err != nil {
t.Fatalf("write fact: %v", err)
}
if phrase := h.detectPattern(ctx, factID, "cat_water", "refill", last); phrase == "" {
t.Fatal("expected a parked routine proposal")
}
if _, handled := h.resolveConfirm(ctx, "нет"); !handled {
t.Fatal("the spoken no should be consumed by the routine confirm")
}
rows, err := st.ListProposedRoutinesByStatus(ctx, store.RoutineDismissed)
if err != nil {
t.Fatalf("list dismissed: %v", err)
}
if len(rows) != 1 {
t.Fatalf("dismissed routines = %d, want 1", len(rows))
}
}
+4 -7
View File
@@ -1210,13 +1210,10 @@ func routineRows(rs []ipc.ProposedRoutine) []routineRow {
return out
}
// acceptRoutine creates the recurring reminder for a proposal, then marks the
// proposal accepted and links the reminder to it. Weekly patterns get a cron
// expression; any other interval fires once.
//
// TODO(vikunja#46): this mirrors the voice accept path in cmd/mavend/voice.go.
// When the tick loop learns to read accepted proposals directly, both callers
// should hand off to one place in core instead of each building a reminder.
// acceptRoutine marks a proposal accepted. This page is the ONLY surface that
// may do it (Vikunja #367): accepting gives the tick loop a standing new
// reason to speak, which DESIGN.md puts at layer 3, and the button here is
// behind step-up. Voice can park the question and dismiss, never accept.
func acceptRoutine(ctx context.Context, core ipc.CoreAPI, id int64) error {
proposed, err := core.ListProposedRoutines(ctx)
if err != nil {
+8 -2
View File
@@ -534,8 +534,14 @@ func (p *LLMPhraser) PhraseReminder(ctx context.Context, d loop.ReminderDecision
text = "reminder"
}
// Russian, like the other two prompts (Vikunja #404). Asking a model for a
// Russian reply in English is asking it to switch languages mid-prompt,
// and a 1.7B sometimes answers in the language it was asked in. The
// persona rules and the JSON contract are not repeated here: this call
// goes through chat(), so nudgeSystem already states both, and a second
// statement of the same contract is one more thing that can drift.
prompt := fmt.Sprintf(
`The user set a reminder: "%s". Rephrase it briefly as a gentle nudge. Respond as JSON: {"response": "...", "mood": "..."}`,
`Он поставил напоминание: "%s". Скажи это своими словами, коротко и мягко — одно предложение.`,
text,
)
resp, err := p.chat(ctx, prompt)
@@ -754,7 +760,7 @@ func (p *LLMPhraser) querySystemPrompt() string {
base := "Ты отвечаешь ему по источникам, которые тебе дали. Отвечай ТОЛЬКО по ним: всё, что ты говоришь, должно быть написано в источниках. " +
"Если ответа в них нет — так и скажи и на этом остановись; не добавляй ничего из своих знаний и не догадывайся. " +
"Не приплетай прошлые реплики разговора. " +
"Отвечай по-русски, коротко и своими словами, начинай с \"вот что я нашла: \". О себе — в женском роде, глаголы в прошедшем времени с окончанием -ла. Он мужчина, обращайся к нему на \"ты\". Respond ONLY with valid JSON: {\"response\": \"...\", \"mood\": \"neutral\"}."
"Отвечай по-русски, коротко и своими словами, начинай с \"вот что я нашла: \". О себе — в женском роде, глаголы в прошедшем времени с окончанием -ла. Он мужчина, обращайся к нему на \"ты\". Отвечай ТОЛЬКО одним объектом JSON: {\"response\": \"...\", \"mood\": \"neutral\"}."
return persona.Prepend(p.cfg.ContextBlock, base)
}
+1 -1
View File
@@ -6,5 +6,5 @@ func KnowledgePrompt() string {
// No self-introduction here: the shared persona block already says who she
// is, and this line used to disagree with it — a different name ("Мавена")
// and a masculine noun ("ассистент") in front of a feminine persona.
return `Ответь кратко из своих знаний. Если не знаешь — скажи "не знаю". Не выдумывай. Respond ONLY with valid JSON: {"response": "...", "mood": "neutral"}.`
return `Ответь кратко из своих знаний. Если не знаешь — скажи "не знаю". Не выдумывай. Отвечай ТОЛЬКО одним объектом JSON: {"response": "...", "mood": "neutral"}.`
}