@agentprojectcontext/apx 1.34.0 → 1.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/package.json +1 -1
  2. package/skills/apx/SKILL.md +1 -1
  3. package/src/core/agent/build-agent-system.js +134 -58
  4. package/src/core/agent/channels/voice-context.js +4 -4
  5. package/src/core/agent/prompt-builder.js +176 -123
  6. package/src/core/agent/prompts/channels/code.md +12 -10
  7. package/src/core/agent/prompts/channels/desktop.md +5 -32
  8. package/src/core/agent/prompts/channels/telegram.md +4 -15
  9. package/src/core/agent/prompts/channels/web_code.md +11 -11
  10. package/src/core/agent/prompts/core/agent-base.md +24 -0
  11. package/src/core/agent/prompts/core/project-agent.md +11 -0
  12. package/src/core/agent/prompts/core/super-agent.md +21 -0
  13. package/src/core/agent/prompts/discipline/action.md +10 -0
  14. package/src/core/agent/prompts/discipline/single-segment.md +6 -0
  15. package/src/core/agent/prompts/discipline/two-segment.md +11 -0
  16. package/src/core/agent/self-memory.js +43 -1
  17. package/src/core/agent/skills/index-store.js +307 -0
  18. package/src/core/agent/skills/index.js +15 -1
  19. package/src/core/agent/skills/inspector.js +317 -0
  20. package/src/core/agent/super-agent.js +7 -1
  21. package/src/core/agent/tools/handlers/_git.js +50 -0
  22. package/src/core/agent/tools/handlers/git-diff.js +44 -0
  23. package/src/core/agent/tools/handlers/git-log.js +38 -0
  24. package/src/core/agent/tools/handlers/git-show.js +34 -0
  25. package/src/core/agent/tools/handlers/git-status.js +61 -0
  26. package/src/core/agent/tools/names.js +31 -0
  27. package/src/core/agent/tools/registry.js +36 -5
  28. package/src/core/config/index.js +21 -0
  29. package/src/core/runtime-skills/apx/SKILL.md +27 -39
  30. package/src/core/runtime-skills/apx-agency-agents/SKILL.md +40 -56
  31. package/src/core/runtime-skills/apx-agent/SKILL.md +27 -30
  32. package/src/core/runtime-skills/apx-mcp/SKILL.md +31 -36
  33. package/src/core/runtime-skills/apx-mcp-builder/SKILL.md +37 -51
  34. package/src/core/runtime-skills/apx-project/SKILL.md +20 -29
  35. package/src/core/runtime-skills/apx-routine/SKILL.md +34 -47
  36. package/src/core/runtime-skills/apx-runtime/SKILL.md +32 -50
  37. package/src/core/runtime-skills/apx-sessions/SKILL.md +96 -145
  38. package/src/core/runtime-skills/apx-skill-builder/SKILL.md +53 -77
  39. package/src/core/runtime-skills/apx-task/SKILL.md +18 -21
  40. package/src/core/runtime-skills/apx-telegram/SKILL.md +43 -54
  41. package/src/core/runtime-skills/apx-voice/SKILL.md +36 -56
  42. package/src/core/stores/conversations.js +27 -2
  43. package/src/host/daemon/api/exec.js +2 -0
  44. package/src/host/daemon/api/skills.js +140 -6
  45. package/src/host/daemon/api/super-agent.js +56 -1
  46. package/src/host/daemon/index.js +17 -0
  47. package/src/interfaces/cli/branding.js +53 -0
  48. package/src/interfaces/cli/commands/skills.js +254 -0
  49. package/src/interfaces/cli/index.js +84 -2
  50. package/src/interfaces/web/dist/assets/index-Cm0KyPoZ.css +1 -0
  51. package/src/interfaces/web/dist/assets/index-DJKA763h.js +628 -0
  52. package/src/interfaces/web/dist/assets/index-DJKA763h.js.map +1 -0
  53. package/src/interfaces/web/dist/index.html +2 -2
  54. package/src/interfaces/web/src/App.tsx +0 -1
  55. package/src/interfaces/web/src/components/chat/ChatList.tsx +412 -0
  56. package/src/interfaces/web/src/components/chat/MessageBubble.tsx +21 -1
  57. package/src/interfaces/web/src/components/settings/AppearancePanel.tsx +1 -1
  58. package/src/interfaces/web/src/components/settings/MemoryPanel.tsx +69 -1
  59. package/src/interfaces/web/src/components/settings/SkillsInspectorPanel.tsx +222 -0
  60. package/src/interfaces/web/src/hooks/useChat.ts +54 -2
  61. package/src/interfaces/web/src/i18n/en.ts +12 -1
  62. package/src/interfaces/web/src/i18n/es.ts +12 -1
  63. package/src/interfaces/web/src/lib/api/agents.ts +1 -1
  64. package/src/interfaces/web/src/lib/api/skills.ts +70 -0
  65. package/src/interfaces/web/src/screens/ProjectScreen.tsx +3 -5
  66. package/src/interfaces/web/src/screens/SettingsScreen.tsx +12 -6
  67. package/src/interfaces/web/src/screens/modules/VoiceScreen.tsx +1 -1
  68. package/src/interfaces/web/src/screens/project/ChatTab.tsx +120 -87
  69. package/src/interfaces/web/src/types/daemon.ts +10 -0
  70. package/src/core/agent/prompts/action-discipline.md +0 -24
  71. package/src/core/agent/prompts/super-agent-base.md +0 -42
  72. package/src/interfaces/web/dist/assets/index-DdmSRtsz.css +0 -1
  73. package/src/interfaces/web/dist/assets/index-M4FspaCH.js +0 -613
  74. package/src/interfaces/web/dist/assets/index-M4FspaCH.js.map +0 -1
  75. package/src/interfaces/web/src/screens/project/ThreadsTab.tsx +0 -100
@@ -4,11 +4,11 @@ import useSWR from "swr";
4
4
  import { Plus, Trash2 } from "lucide-react";
5
5
  import { Agents } from "../../lib/api";
6
6
  import { Badge, Button, Dialog, Empty, Field, Input, Loading, Switch } from "../../components/ui";
7
- import { UiSelect } from "../../components/UiSelect";
8
7
  import { Composer } from "../../components/chat/Composer";
9
8
  import { MessageList } from "../../components/chat/MessageList";
10
9
  import { ContextBar } from "../../components/chat/ContextBar";
11
10
  import { InlineAskPanel, pendingAskQuestions } from "../../components/chat/InlineAskPanel";
11
+ import { ChatList, type ChatKey } from "../../components/chat/ChatList";
12
12
  import { useChat } from "../../hooks/useChat";
13
13
  import { useToast } from "../../components/Toast";
14
14
  import { t } from "../../i18n";
@@ -24,48 +24,56 @@ export function ChatTab({ pid }: { pid: string }) {
24
24
  const toast = useToast();
25
25
  const [params] = useSearchParams();
26
26
  const agents = useSWR(`/projects/${pid}/agents`, () => Agents.list(pid));
27
- const [activeSlug, setActiveSlug] = useState(params.get("agent") || "");
28
27
  const [creating, setCreating] = useState(false);
29
28
  const [model, setModel] = useState("");
30
29
  const [dismissedAskKey, setDismissedAskKey] = useState<string | null>(null);
31
- const { msgs, send: sendChat, stop, clear, streaming } = useChat(pid, (m) => toast.error(m));
30
+ const { msgs, send: sendChat, stop, clear, load, streaming, conversationId } =
31
+ useChat(pid, (m) => toast.error(m));
32
32
  const persona = usePersonaName();
33
33
 
34
+ // Selection state — drives both the sidebar highlight and the right-pane
35
+ // header. Defaults to a live session with the super-agent so the chat works
36
+ // even on a brand-new project with zero agents and zero conversations.
37
+ const initialFromUrl = params.get("agent");
38
+ const [selected, setSelected] = useState<ChatKey>(
39
+ initialFromUrl
40
+ ? { kind: "live", agentSlug: initialFromUrl }
41
+ : { kind: "live", agentSlug: ROBY_SLUG },
42
+ );
43
+
34
44
  const agentList = agents.data || [];
35
- // Virtual options shown in the dropdown — the super-agent is always first,
36
- // then the real project agents. It works on every project (calls
37
- // /projects/:pid/super-agent/chat) so we expose it everywhere, not just /base.
38
45
  const isRoby = (slug: string | null | undefined) => slug === ROBY_SLUG;
39
- const dropdownOptions = useMemo(
40
- () => [
41
- { value: ROBY_SLUG, label: `${persona} (super-agent)` },
42
- ...agentList.map((a) => ({ value: a.slug, label: a.slug })),
43
- ],
44
- [agentList, persona],
45
- );
46
+
47
+ // The agent whose dropdown badge / model we show on the right header.
46
48
  const activeAgent = useMemo(
47
- () => agentList.find((a) => a.slug === activeSlug) || agentList[0],
48
- [agentList, activeSlug],
49
+ () =>
50
+ selected.kind === "live"
51
+ ? agentList.find((a) => a.slug === selected.agentSlug)
52
+ : agentList.find((a) => a.slug === selected.agentSlug),
53
+ [agentList, selected],
49
54
  );
50
- // Effective slug we'll send with: Roby if explicitly selected, or the first
51
- // real agent, or Roby when the project has no agents at all.
52
- const effectiveSlug = isRoby(activeSlug)
53
- ? ROBY_SLUG
54
- : (activeAgent?.slug || ROBY_SLUG);
55
- const activeIsRoby = effectiveSlug === ROBY_SLUG;
55
+ const activeIsRoby = isRoby(selected.agentSlug);
56
56
 
57
+ // Whenever the user picks a stored conversation, reload the in-memory chat
58
+ // with its persisted history. The hook itself binds the conversation_id so
59
+ // subsequent sends append to the same file.
57
60
  useEffect(() => {
58
- if (!activeSlug && activeAgent?.slug) setActiveSlug(activeAgent.slug);
59
- }, [activeAgent?.slug, activeSlug]);
60
-
61
- const resetConversation = () => clear();
61
+ if (selected.kind === "conv") {
62
+ void load(selected.agentSlug, selected.convId);
63
+ } else {
64
+ // Switching to a live session = drop any previously bound conversation.
65
+ if (conversationId) clear();
66
+ }
67
+ // eslint-disable-next-line react-hooks/exhaustive-deps
68
+ }, [selected.kind, selected.kind === "conv" ? selected.convId : selected.agentSlug]);
62
69
 
63
70
  const send = async (text: string) => {
64
- if (!activeIsRoby && !activeAgent) return;
65
- await sendChat(text, {
66
- model: model || undefined,
67
- agentSlug: activeIsRoby ? undefined : activeAgent!.slug,
68
- });
71
+ if (activeIsRoby) {
72
+ await sendChat(text, { model: model || undefined });
73
+ return;
74
+ }
75
+ if (!activeAgent) return;
76
+ await sendChat(text, { model: model || undefined, agentSlug: activeAgent.slug });
69
77
  };
70
78
 
71
79
  const copyToClipboard = async (text: string) => {
@@ -73,72 +81,97 @@ export function ChatTab({ pid }: { pid: string }) {
73
81
  catch { /* ignore */ }
74
82
  };
75
83
 
76
- if (agents.isLoading) return <Loading />;
84
+ const onNewChat = () => {
85
+ setSelected({ kind: "live", agentSlug: ROBY_SLUG });
86
+ clear();
87
+ };
77
88
 
89
+ const headerTitle = activeIsRoby
90
+ ? t("project.chat.superagent_title", { persona })
91
+ : selected.kind === "conv"
92
+ ? selected.convId
93
+ : t("project.chat.title");
78
94
  const headerSubtitle = activeIsRoby
79
95
  ? t("project.chat.superagent_subtitle", { persona })
80
- : t("project.chat.subtitle");
96
+ : selected.kind === "conv"
97
+ ? t("project.chat.loaded_subtitle", { slug: selected.agentSlug })
98
+ : t("project.chat.subtitle");
99
+
100
+ if (agents.isLoading) return <Loading />;
81
101
 
82
102
  return (
83
- <div className="flex h-full flex-col overflow-hidden rounded-xl border border-border bg-card/40">
84
- <header className="flex shrink-0 items-center justify-between gap-3 border-b border-border px-4 py-3">
85
- <div className="min-w-0">
86
- <h2 className="text-sm font-semibold">
87
- {activeIsRoby ? t("project.chat.superagent_title", { persona }) : t("project.chat.title")}
88
- </h2>
89
- <p className="truncate text-[11px] text-muted-fg">{headerSubtitle}</p>
90
- </div>
91
- <div className="flex items-center gap-2">
92
- <div className="w-52">
93
- <UiSelect
94
- value={effectiveSlug}
95
- onChange={(v) => { setActiveSlug(v); resetConversation(); }}
96
- options={dropdownOptions}
97
- />
103
+ <div className="flex h-full overflow-hidden rounded-xl border border-border bg-card/40">
104
+ <ChatList
105
+ pid={pid}
106
+ agents={agentList}
107
+ superAgentSlug={ROBY_SLUG}
108
+ superAgentLabel={`${persona} (super-agent)`}
109
+ selected={selected}
110
+ onSelect={setSelected}
111
+ onNewChat={onNewChat}
112
+ />
113
+
114
+ <section className="flex min-w-0 flex-1 flex-col">
115
+ <header className="flex shrink-0 items-center justify-between gap-3 border-b border-border px-4 py-3">
116
+ <div className="min-w-0">
117
+ <h2 className="truncate text-sm font-semibold">{headerTitle}</h2>
118
+ <p className="truncate text-[11px] text-muted-fg">{headerSubtitle}</p>
98
119
  </div>
99
- {activeIsRoby
100
- ? <Badge tone="success">super-agent</Badge>
101
- : activeAgent?.model && <Badge tone="info">{activeAgent.model}</Badge>}
102
- {!agentList.length && !activeIsRoby && (
103
- <Button variant="primary" size="sm" onClick={() => setCreating(true)}>
104
- <Plus size={14} /> {t("project.chat.create_agent")}
120
+ <div className="flex items-center gap-2">
121
+ {activeIsRoby ? (
122
+ <Badge tone="success">super-agent</Badge>
123
+ ) : (
124
+ activeAgent?.model && <Badge tone="info">{activeAgent.model}</Badge>
125
+ )}
126
+ {selected.kind === "conv" && <Badge tone="info">{conversationId || "…"}</Badge>}
127
+ {!agentList.length && !activeIsRoby && (
128
+ <Button variant="primary" size="sm" onClick={() => setCreating(true)}>
129
+ <Plus size={14} /> {t("project.chat.create_agent")}
130
+ </Button>
131
+ )}
132
+ <Button
133
+ variant="ghost"
134
+ size="sm"
135
+ disabled={streaming || msgs.length === 0}
136
+ onClick={onNewChat}
137
+ >
138
+ <Trash2 size={13} /> {t("project.chat.clear")}
105
139
  </Button>
140
+ </div>
141
+ </header>
142
+
143
+ <div className="flex-1 overflow-y-auto">
144
+ {msgs.length ? (
145
+ <MessageList msgs={msgs} onCopy={copyToClipboard} />
146
+ ) : (
147
+ <div className="flex h-full items-center justify-center p-8">
148
+ <p className="text-sm text-muted-fg">{t("project.chat.empty")}</p>
149
+ </div>
106
150
  )}
107
- <Button variant="ghost" size="sm" disabled={streaming || msgs.length === 0} onClick={resetConversation}>
108
- <Trash2 size={13} /> {t("project.chat.clear")}
109
- </Button>
110
151
  </div>
111
- </header>
112
- <div className="flex-1 overflow-y-auto">
113
- {msgs.length ? (
114
- <MessageList msgs={msgs} onCopy={copyToClipboard} />
115
- ) : (
116
- <div className="flex h-full items-center justify-center p-8">
117
- <p className="text-sm text-muted-fg">{t("project.chat.empty")}</p>
118
- </div>
119
- )}
120
- </div>
121
- <ContextBar msgs={msgs} />
122
- {(() => {
123
- const pending = !streaming ? pendingAskQuestions(msgs) : null;
124
- if (!pending || pending.turnKey === dismissedAskKey) return null;
125
- return (
126
- <InlineAskPanel
127
- turnKey={pending.turnKey}
128
- questions={pending.questions}
129
- onSubmit={(compiled) => void send(compiled)}
130
- onDismiss={() => setDismissedAskKey(pending.turnKey)}
131
- disabled={streaming}
132
- />
133
- );
134
- })()}
135
- <Composer
136
- onSend={send}
137
- onStop={stop}
138
- streaming={streaming}
139
- model={model}
140
- onModelChange={setModel}
141
- />
152
+ <ContextBar msgs={msgs} />
153
+ {(() => {
154
+ const pending = !streaming ? pendingAskQuestions(msgs) : null;
155
+ if (!pending || pending.turnKey === dismissedAskKey) return null;
156
+ return (
157
+ <InlineAskPanel
158
+ turnKey={pending.turnKey}
159
+ questions={pending.questions}
160
+ onSubmit={(compiled) => void send(compiled)}
161
+ onDismiss={() => setDismissedAskKey(pending.turnKey)}
162
+ disabled={streaming}
163
+ />
164
+ );
165
+ })()}
166
+ <Composer
167
+ onSend={send}
168
+ onStop={stop}
169
+ streaming={streaming}
170
+ model={model}
171
+ onModelChange={setModel}
172
+ />
173
+ </section>
174
+
142
175
  <CreateAgentDialog
143
176
  open={creating}
144
177
  pid={pid}
@@ -284,6 +284,16 @@ export interface ChatStreamEvent {
284
284
  streak?: number;
285
285
  // tool_start / tool_result / tool_deduped
286
286
  trace?: ToolTrace;
287
+ // skill_inspector: which skills the per-turn RAG loaded/hinted this turn
288
+ inspector?: {
289
+ enabled?: boolean;
290
+ reason?: string;
291
+ embedder?: string;
292
+ scored?: { slug: string; sim: number }[];
293
+ loaded?: string[];
294
+ hinted?: string[];
295
+ jit?: boolean;
296
+ };
287
297
  // final
288
298
  result?: {
289
299
  text?: string;
@@ -1,24 +0,0 @@
1
- ## Action Discipline (mandatory)
2
- - NEVER acknowledge an action without executing it in the same turn. If you are going to do something, call the tool FIRST, then report the result.
3
- - NEVER use empty acknowledgments like "Ok", "Got it", "Sure", "Understood", "On it", "Give me a moment", "I'll do that now" as standalone responses when a tool call is expected. These are invalid responses.
4
- - Action first, report after. Produce the tool call in the same response as your acknowledgment.
5
- - If you cannot execute the action (missing permission, unclear params, tool not available), explain WHY — do not promise and disappear.
6
- - If the user asks you to do multiple things, do them all in the same turn using sequential tool calls if needed.
7
-
8
- ## Two-segment turns with tools — intro short, answer substantive (mandatory)
9
- A turn that calls one or more tools produces TWO text segments shown to the user:
10
-
11
- 1. **Pre-tool intro** — a SHORT, NATURAL filler in the user's language BEFORE the tool runs. 2 to 8 words. NEVER contains the answer / data / acknowledgment. Examples: "Dale, voy a anotar eso", "Reviso eso", "Un momento, busco", "Going to remember that".
12
- 2. **Post-tool answer** — the SUBSTANTIVE result AFTER the tool returns. Carries the data, the confirmation, or the next question. Examples: "Listo, anoté que sos Tech Lead en Bytetravel.", "Encontré 3 routines activas: …".
13
-
14
- Hard rules:
15
- - The pre-tool intro NEVER includes the substantive content. Do NOT say "Anoté que sos Tech Lead" BEFORE the remember tool runs — at that point the tool hasn't executed yet.
16
- - The post-tool answer NEVER restates what the intro already said. They serve different purposes: the intro is filler, the answer is the result.
17
- - Greet AT MOST ONCE per turn. If you already opened with "hola" in the intro, the answer starts with the actual result, no greeting.
18
- - A turn with NO tool calls produces a single segment — go straight to the answer, no filler intro needed.
19
- - A simple chit-chat reply (no tool) is one segment: the reply itself.
20
-
21
- ## Chit-chat & greetings (only path out of a forced tool turn)
22
- - If the user is just greeting, chatting, or thanking you with NO actionable request ("hola", "hi", "buenas", "gracias", "👍", "ok"), you must STILL satisfy the tool-choice contract: call `finish` with a brief friendly reply in the user's language. Do NOT call any other tool just because tools are available — `finish` is the correct tool for chit-chat.
23
- - A greeting that piggybacks a real request ("hola, listame las rutinas") is NOT chit-chat — handle the request normally with the right tool.
24
- - When in doubt between chit-chat and a vague request, ask ONE short clarifying question via `finish` — never invent a topic or run an unrelated tool to "be useful".
@@ -1,42 +0,0 @@
1
- # Role
2
- You are **APX itself**, the default daemon-level action agent for Agent Project Context (APC). In code/CLI this role is the *super-agent* — that's the **mode** ("the APX agent talking when no project agent was named"), not your name. Your real name, owner, language, timezone and locale come from the **User & identity** section below; never call yourself "super-agent" to the user.
3
-
4
- You are an **action agent**, not a chatbot or code explainer: you USE TOOLS to do real things on the user's system. APX (the daemon, config, projects, agents, sessions, message logs) is your own body and toolbox — speak in the first person ("my sessions", "let me check", "I ran…"), never as a third party ("APX can…"). Assume you can do what's asked and reach for the right tool; don't hedge about limits until a tool actually fails.
5
-
6
- If a message starts with "[audio]", the rest is a speech transcription — treat it as the user's normal message.
7
-
8
- # Tools
9
- The runtime sends your exact callable tool schemas on every turn — that is your real capability list. Use them; never recite a tool catalog at the user. On lightweight channels only a core subset is sent; the rest still exist — pull them in by acting, or via load_skill. For factual/inventory questions, CALL a tool first; don't ask the user to specify a project unless the tool fails.
10
-
11
- You also ship **apx-\* skills** with the exact syntax for multi-step APX operations (routines, projects, MCPs, agents, telegram, runtimes, tasks, voice). When the user needs precise syntax/behavior for one of these, `load_skill` the matching one BEFORE running commands — don't guess flags or invent cron grammar, ports, or paths.
12
-
13
- # What you must NOT do
14
- - Don't explain code or describe what a tool *would* do — call it and report the result.
15
- - Don't give AI disclaimers. You DO have history and memory (see Memory below) — never say "I have no memory of past conversations".
16
- - Don't tell the user to run an `apx …` command to get info you can fetch with a tool. You operate APX; run the tool yourself. (Only mention CLI when they explicitly ask "how do I do this from the terminal?")
17
- - Don't end with "give me a second" / "I'll try later", and don't reply with a bare "ok"/"checking"/"one moment". Every message carries a result, finding, or one concrete question.
18
- - If a message is short or ambiguous, ask ONE short clarifying question in the user's language — don't invent a topic.
19
-
20
- # Memory & history
21
- You have durable memory across all channels — never deny it. Two sources:
22
- - **Sessions & chat logs**: when asked what you worked on, about a "previous/last session", or "what we talked about", call `search_sessions` (defaults to your own apx sessions — pass `engine` only when the user names claude/codex, `all:true` only when they want every engine; pass `id` to open a transcript) and/or `search_messages`. Answer didactically in prose (in the user's language — e.g. "last time we worked on X and Y"), not as a raw list of titles. If your sessions are thin, say so and offer to look across engines — never conclude you "have no history".
23
- - **Your notebook (self-memory)**: `~/.apx/memory.md`, a bounded slice injected above (as "# Your notebook" or folded into "# Relevant memory"). At the end of any turn where something durable happened (a decision, a completed task, an agreed fact), save the gist with `remember` so your other channels know it too. Keep notes to one self-contained sentence. Use `create_task` for one-off TODOs and project-agent memory for project-scoped facts. When a "# Relevant memory" block is present, treat its bullets as known facts; if a fresh chat opens and something there is still open, bring it up naturally in the user's language (e.g. "yesterday we were on X — shall we continue?") — weave in only what's relevant, don't dump the block.
24
-
25
- # How you operate
26
- - APC projects live anywhere on disk; the default workspace is APX home, not a user repo. Registered projects appear below as a tiny index — call tools for details.
27
- - Permission mode is injected in its own section. total = execute freely. automatico = read/list/safe read-only shell (apx --help, ls, find, rg, grep, docker ps) run directly; destructive/external/runtime/MCP/outbound/config/filesystem-mutating actions need explicit confirmation. permiso = only allowed_tools run directly; the rest need confirmation. When a tool schema has `confirmed`, set confirmed=true only after explicit user confirmation for that exact action.
28
- - Filesystem search: use targeted tools (find, fd, rg, grep -rn, concrete globs) — never `ls -R` on large trees.
29
- - Register projects with add_project only — never hand-write AGENTS.md or .apc/project.json via shell.
30
- - Never paste base64/data-URIs in message text — send images/audio/files via send_telegram media params or paths.
31
-
32
- # Hard rules
33
- 1. NEVER invent project names, agent slugs, model ids, MCP names, or paths. Look them up via list_* first.
34
- 2. Inventory requests with no project mean **all projects** — call the tool with no project argument; never answer "specify a project" when a global list tool exists.
35
- 3. If a tool errors, retry with different arguments before asking the user.
36
- 4. Write in the user's configured language (see User & identity). Follow the Channel context formatting rules when present. Stay concise unless asked for detail.
37
- 5. Prior turns disambiguate references only ("the first one" → earlier mention); re-call tools for any factual data — past turns are not a cache. /reset or /new means answer fresh.
38
- 6. **SELF-RUN**: "yourself"/"same"/"default"/no agent named → act as APX; don't call list_agents, don't pass an agent argument. **DELEGATE**: a named APC agent → call_agent. **DISPATCH**: an external runtime (claude-code, codex…) → call_runtime, passing the named agent or omitting it to run as yourself. **VAULT**: new agent from a template → list_vault_agents, then import_agent if there's a match.
39
- 7. **Projects**: no project named → use the default workspace. EXCEPTION — routines and project-scoped work need a REAL project: if asked to create a routine (or agent/memory) without a named project, ask which one. Never create routines in the default/id=0 workspace.
40
- 8. **Identity**: user changes their name/your name/personality → set_identity, then confirm.
41
- 9. **Skills on demand**: load_skill only when the user needs exact syntax/behavior matching a skill description (pass project_path from CWD when present) — not for unrelated questions.
42
- 10. **CWD**: when Channel context includes `CWD: <path>`, "this directory/project/here" means that path — use it directly, don't ask.