ticlawk 0.1.17-dev.11 → 0.1.17-dev.13

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.
package/README.md CHANGED
@@ -230,8 +230,8 @@ Commands:
230
230
  profile list or switch saved local identities
231
231
  message send/read chat messages (agent CLI surface)
232
232
  task claim/update/list tasks (agent CLI surface)
233
- goal report FSM transitions / signal goal changes (agent CLI surface)
234
- approval canonical goal-loop approval request/resolve (agent CLI surface)
233
+ goal report FSM transitions (agent CLI surface)
234
+ approval resolve/list goal-loop approvals (agent CLI surface)
235
235
  charter get/set conversation goal and role spec (agent CLI surface)
236
236
  group create/list/delete groups, manage charters/members (agent CLI surface)
237
237
  dashboard set/get conversation dashboards (agent CLI surface)
package/bin/ticlawk.mjs CHANGED
@@ -57,9 +57,7 @@ import {
57
57
  runMessageCheckCommand,
58
58
  runMessageReactCommand,
59
59
  runMessageReadCommand,
60
- runGoalChangedCommand,
61
60
  runGoalReportCommand,
62
- runApprovalRequestCommand,
63
61
  runApprovalResolveCommand,
64
62
  runApprovalListCommand,
65
63
  runMessageSearchCommand,
@@ -134,8 +132,8 @@ Commands:
134
132
  profile list or switch saved local identities
135
133
  message send/read chat messages (agent CLI surface)
136
134
  task claim/update/list tasks (agent CLI surface)
137
- goal report FSM transitions / signal goal changes (agent CLI surface)
138
- approval canonical goal-loop approval request/resolve (agent CLI surface)
135
+ goal report FSM transitions (agent CLI surface)
136
+ approval resolve/list goal-loop approvals (agent CLI surface)
139
137
  charter get/set conversation goal and role spec (agent CLI surface)
140
138
  group create/list/delete groups, manage charters/members (agent CLI surface)
141
139
  dashboard set/get conversation dashboards (agent CLI surface)
@@ -487,10 +485,6 @@ async function main() {
487
485
  process.exitCode = await runGoalReportCommand(args);
488
486
  return;
489
487
  }
490
- if (sub === 'changed') {
491
- process.exitCode = await runGoalChangedCommand(args);
492
- return;
493
- }
494
488
  console.error(`unknown goal subcommand: ${sub}`);
495
489
  process.exit(1);
496
490
  }
@@ -501,10 +495,6 @@ async function main() {
501
495
  console.log(AGENT_COMMAND_HELP.approval);
502
496
  return;
503
497
  }
504
- if (sub === 'request') {
505
- process.exitCode = await runApprovalRequestCommand(args);
506
- return;
507
- }
508
498
  if (sub === 'resolve') {
509
499
  process.exitCode = await runApprovalResolveCommand(args);
510
500
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ticlawk",
3
- "version": "0.1.17-dev.11",
3
+ "version": "0.1.17-dev.13",
4
4
  "description": "Local connector that links agent harnesses (Claude Code, Codex, OpenClaw, opencode, Pi) to the Ticlawk mobile app.",
5
5
  "type": "module",
6
6
  "main": "ticlawk.mjs",
@@ -302,31 +302,6 @@ export async function reportGoalTransition({
302
302
  });
303
303
  }
304
304
 
305
- export async function noteGoalChanged({ actingAgentId, conversationId }) {
306
- return apiFetch('/api/agent/goal/changed', {
307
- method: 'POST',
308
- body: JSON.stringify({
309
- acting_as_agent_id: actingAgentId,
310
- conversation_id: conversationId,
311
- }),
312
- });
313
- }
314
-
315
- export async function requestGoalApproval({
316
- actingAgentId, conversationId, title, detail, ttlSeconds,
317
- }) {
318
- return apiFetch('/api/agent/approval/request', {
319
- method: 'POST',
320
- body: JSON.stringify({
321
- acting_as_agent_id: actingAgentId,
322
- conversation_id: conversationId,
323
- title,
324
- detail: detail ?? null,
325
- ttl_seconds: ttlSeconds ?? null,
326
- }),
327
- });
328
- }
329
-
330
305
  export async function listGoalApprovals({ actingAgentId, conversationId }) {
331
306
  const params = new URLSearchParams();
332
307
  params.set('acting_as_agent_id', actingAgentId);
@@ -383,51 +383,6 @@ export async function runGoalReportCommand(args) {
383
383
  return exitFromStatus(res.statusCode);
384
384
  }
385
385
 
386
- export async function runGoalChangedCommand(args) {
387
- const env = requireAgentEnv();
388
- const conversationId = getArg(args, 'conversation') || getArg(args, 'conversation-id') || env.currentConversationId;
389
- if (!conversationId) {
390
- console.error('--conversation is required');
391
- return 2;
392
- }
393
- const res = await daemonRequest({
394
- method: 'POST',
395
- path: '/agent/goal/changed',
396
- headers: commonHeaders(env),
397
- body: { conversation_id: conversationId },
398
- });
399
- printJson(res.body);
400
- return exitFromStatus(res.statusCode);
401
- }
402
-
403
- export async function runApprovalRequestCommand(args) {
404
- const env = requireAgentEnv();
405
- const conversationId = getArg(args, 'conversation') || getArg(args, 'conversation-id') || env.currentConversationId;
406
- const title = getArg(args, 'title');
407
- if (!conversationId) {
408
- console.error('--conversation is required');
409
- return 2;
410
- }
411
- if (!title) {
412
- console.error('--title is required');
413
- return 2;
414
- }
415
- const ttlRaw = getArg(args, 'ttl-seconds');
416
- const res = await daemonRequest({
417
- method: 'POST',
418
- path: '/agent/approval/request',
419
- headers: commonHeaders(env),
420
- body: {
421
- conversation_id: conversationId,
422
- title,
423
- detail: getArg(args, 'detail'),
424
- ttl_seconds: ttlRaw != null ? Number(ttlRaw) : undefined,
425
- },
426
- });
427
- printJson(res.body);
428
- return exitFromStatus(res.statusCode);
429
- }
430
-
431
386
  export async function runApprovalResolveCommand(args) {
432
387
  const env = requireAgentEnv();
433
388
  const requestId = getArg(args, 'request') || getArg(args, 'request-id');
@@ -1481,7 +1436,7 @@ export const AGENT_COMMAND_HELP = {
1481
1436
  Compatibility alias for group admin commands. Prefer \`ticlawk group ...\`
1482
1437
  for groups and \`ticlawk charter ...\` for conversation charters.
1483
1438
  `,
1484
- goal: `ticlawk goal <report|changed>
1439
+ goal: `ticlawk goal <report>
1485
1440
  Goal-lane (FSM) control. Normally invoked from a goal-step turn, not by hand.
1486
1441
  ticlawk goal report --transition <id> --outcome <outcome> [--conversation <id>] [--detail <text>] [--current-task <task-id>]
1487
1442
  Report the outcome of the current FSM step and advance the state machine.
@@ -1490,16 +1445,14 @@ export const AGENT_COMMAND_HELP = {
1490
1445
  gap_analysis: gap | no_gap | wait
1491
1446
  execute: task_completed | needs_approval | blocked
1492
1447
  review: accepted | rejected
1493
- ticlawk goal changed [--conversation <id>]
1494
- Signal that the conversation's goal changed; wakes the FSM into gap analysis.
1448
+ (To change a goal, write the charter with \`ticlawk charter set\` — that re-aims the goal lane.)
1495
1449
  `,
1496
- approval: `ticlawk approval <request|resolve|list>
1497
- Canonical goal-loop approval flow. The goal lane parks on an approval and is
1498
- resumed by exactly one idempotent decision (button token OR chat resolver).
1499
- ticlawk approval request --title "<text>" [--conversation <id>] [--detail <text>] [--ttl-seconds <n>]
1500
- Park a pending owner approval on the goal_session (after EXECUTE reports
1501
- needs_approval). Returns request_id plus a one-time button action token.
1502
- --conversation defaults to the current goal-turn conversation.
1450
+ approval: `ticlawk approval <resolve|list>
1451
+ Goal-loop approval resolution (chat lane). The goal lane PARKS an approval by
1452
+ publishing an approval briefing (\`ticlawk briefing publish --mode approval\`),
1453
+ which both asks the owner and suspends the loop on it. The owner answers by
1454
+ tapping approve (which posts an "approved" message) or replying in plain
1455
+ language either way the chat lane resolves it here, idempotently.
1503
1456
  ticlawk approval list [--conversation <id> | --target "<target>"]
1504
1457
  List pending approval requests in the conversation. Use this from the chat
1505
1458
  lane to find which request an owner's natural-language approval refers to.
@@ -416,51 +416,6 @@ export async function handleGoalReport(req, body, ctx) {
416
416
  }
417
417
  }
418
418
 
419
- export async function handleGoalChanged(req, body, ctx) {
420
- const actingAgentId = getActingAgentId(req, body);
421
- const v = validateActingAgent(actingAgentId, ctx);
422
- if (!v.ok) return { status: v.status, body: { error: v.error } };
423
- const conversationId = body?.conversation_id || getCurrentConversationId(req, body);
424
- if (!conversationId) return { status: 400, body: { error: 'conversation_id is required' } };
425
- try {
426
- const data = await api.noteGoalChanged({ actingAgentId, conversationId });
427
- debugLog('agent-cli', 'goal.changed', {
428
- actingAgentId,
429
- conversationId,
430
- goalVersion: data?.goal_version,
431
- });
432
- return { status: data?.ok ? 200 : 400, body: data };
433
- } catch (err) {
434
- return { status: err?.status || 500, body: { error: err?.message || 'goal changed failed' } };
435
- }
436
- }
437
-
438
- export async function handleApprovalRequest(req, body, ctx) {
439
- const actingAgentId = getActingAgentId(req, body);
440
- const v = validateActingAgent(actingAgentId, ctx);
441
- if (!v.ok) return { status: v.status, body: { error: v.error } };
442
- const conversationId = body?.conversation_id || getCurrentConversationId(req, body);
443
- if (!conversationId) return { status: 400, body: { error: 'conversation_id is required' } };
444
- if (!body?.title) return { status: 400, body: { error: 'title is required' } };
445
- try {
446
- const data = await api.requestGoalApproval({
447
- actingAgentId,
448
- conversationId,
449
- title: body.title,
450
- detail: body.detail || null,
451
- ttlSeconds: body.ttl_seconds || null,
452
- });
453
- debugLog('agent-cli', 'approval.request', {
454
- actingAgentId,
455
- conversationId,
456
- requestId: data?.request_id,
457
- });
458
- return { status: data?.ok ? 200 : 400, body: data };
459
- } catch (err) {
460
- return { status: err?.status || 500, body: { error: err?.message || 'approval request failed' } };
461
- }
462
- }
463
-
464
419
  export async function handleApprovalList(req, query, ctx) {
465
420
  const actingAgentId = getActingAgentId(req, query);
466
421
  const v = validateActingAgent(actingAgentId, ctx);
package/src/core/http.mjs CHANGED
@@ -39,9 +39,7 @@ import {
39
39
  handleReminderSchedule,
40
40
  handleReminderSnooze,
41
41
  handleReminderUpdate,
42
- handleGoalChanged,
43
42
  handleGoalReport,
44
- handleApprovalRequest,
45
43
  handleApprovalResolve,
46
44
  handleApprovalList,
47
45
  handleServerInfo,
@@ -155,18 +153,6 @@ export function startLocalHttpServer({ port, adapter, ctx }) {
155
153
  const r = await handleGoalReport(req, body, cliCtx);
156
154
  return writeJson(res, r.status, r.body);
157
155
  }
158
- if (urlNoQuery === '/agent/goal/changed' && method === 'POST') {
159
- const body = await readJsonBody(req);
160
- if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
161
- const r = await handleGoalChanged(req, body, cliCtx);
162
- return writeJson(res, r.status, r.body);
163
- }
164
- if (urlNoQuery === '/agent/approval/request' && method === 'POST') {
165
- const body = await readJsonBody(req);
166
- if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
167
- const r = await handleApprovalRequest(req, body, cliCtx);
168
- return writeJson(res, r.status, r.body);
169
- }
170
156
  if (urlNoQuery === '/agent/approval/resolve' && method === 'POST') {
171
157
  const body = await readJsonBody(req);
172
158
  if (body === null) return writeJson(res, 400, { error: 'invalid json body' });
@@ -24,10 +24,10 @@ const STEP_GUIDES = {
24
24
  },
25
25
  execute: {
26
26
  title: 'EXECUTE',
27
- body: `Do the next concrete unit of work toward the goal (or drive the current task to completion). Send routine interim progress with \`ticlawk message send --phase progress\`; if an update clears the briefing bar below (e.g. it is a result the owner explicitly asked to be told about), surface it as a briefing instead.
27
+ body: `Do the next concrete unit of work toward the goal (or drive the current task to completion). Keep progress on the dashboard (\`ticlawk dashboard set\`); the goal lane reaches the owner only through the dashboard and briefings never \`message send\`.
28
28
  - When the unit of work is finished and ready to be checked, report outcome=task_completed.
29
- - If you cannot proceed without an owner approval, decision, or permission, park ONE canonical approval with \`ticlawk approval request --title "<what you need approved>" [--detail "<context>"]\`, tell the owner what you need and why, then report outcome=needs_approval. The owner's approval (button or a natural-language reply) resumes you automatically.
30
- - If you are blocked by something else (missing input, external failure, a needed resource), explain it to the owner, then report outcome=blocked.`,
29
+ - If you cannot proceed without something only the owner can grant — a decision, a permission, or approval to spend money or use a resource — ask for it with ONE approval briefing: \`ticlawk briefing publish --mode approval\` (short: what you need and why). That single call both asks the owner and suspends the loop on the approval. Then report outcome=needs_approval. Their answer (tapping approve, or a natural-language reply) resumes you automatically.
30
+ - If you are blocked by something an owner decision cannot fix — an external failure, or a missing input no decision of theirs unblocks — surface it with \`ticlawk briefing publish --mode info\` only if they would be wrong not to know, then report outcome=blocked.`,
31
31
  outcomes: ['task_completed', 'needs_approval', 'blocked'],
32
32
  },
33
33
  review: {
@@ -100,22 +100,18 @@ export function buildGoalStepPrompt(msg) {
100
100
 
101
101
  if (guide) {
102
102
  sections.push([
103
- `[goal_step] You are running the goal loop for this conversation. This is a backend FSM step, not a user message do NOT treat it as something to reply to.`,
103
+ `[goal_step] You are running one step of the goal loop for this conversation a backend FSM step. Act on it and report the outcome; there is no message to reply to.`,
104
104
  ``,
105
105
  `Current step: ${guide.title}`,
106
106
  guide.body,
107
107
  ``,
108
- `Briefing rule (independent of which step you are on): a briefing (\`ticlawk briefing publish\`) interrupts the owner it is only for things worth their attention. Default to NOT sending one; routine progress belongs on the dashboard (the owner pulls it) or a chat \`ticlawk message send\`. Send a briefing only when one of these holds:`,
109
- ` (a) the owner must act or decide — e.g. an approval you parked (\`--mode approval\`);`,
110
- ` (b) the owner asked to be told about this — a standing request, a scheduled time, or a threshold they set (\`--mode info\`);`,
111
- ` (c) something happened the owner would be wrong not to know now — goal done, blocked, materially off-track, or a result they are waiting on (\`--mode info\`).`,
112
- `If you are unsure, it is NOT a briefing — put it on the dashboard. The dashboard is the always-current pull surface; briefings are scarce pushes — over-notifying trains the owner to ignore them.`,
108
+ `Briefings are scarce pushes that interrupt the owner; routine progress belongs on the dashboard (the owner pulls it). Send one (\`ticlawk briefing publish\`) only when the owner must act or decide (\`--mode approval\`), asked to be told, or would be wrong not to know now goal done, blocked, off-track, or a result they are waiting on (\`--mode info\`). The exact bar is in SURFACES.md "Briefing"; if unsure, it is not a briefing.`,
113
109
  ``,
114
110
  `When the step is done, advance the state machine by running EXACTLY ONE report:`,
115
111
  ` ${reportCmd}`,
116
112
  ``,
117
113
  `Reporting the outcome continues the loop: a running next state comes back as a fresh step; with no gap or a wait, the loop parks itself. Report exactly once — do not loop inside this turn.`,
118
- `Reach the owner only through Ticlawk surfaces: \`ticlawk message send --target ${target}\` (chat), \`ticlawk briefing publish\` (push, per the rule above), \`ticlawk dashboard set\` (the goal report). Write owner-facing text for someone reading cold who has never seen your plan or task board: what changed, why it matters, and what (if anything) they must do — naming things by what they are, never by a private code, run name, or task number.`,
114
+ `Reach the owner only through two surfaces: \`ticlawk dashboard set\` (the goal report — routine progress, the owner pulls it) and \`ticlawk briefing publish\` (a scarce push, per the rule above). The goal lane never uses \`ticlawk message send\` — chat is the chat lane's. Write owner-facing text for someone reading cold who has never seen your plan or task board: what changed, why it matters, and what (if anything) they must do — naming things by what they are, never by a private code, run name, or task number.`,
119
115
  `[/goal_step]`,
120
116
  ].join('\n'));
121
117
  } else {
@@ -14,6 +14,6 @@ DO NOT EDIT.
14
14
 
15
15
  `MEMORY.md` is your recovery entry point — read it before acting, and keep it durable, not a log.
16
16
 
17
- - Record only what stays useful across turns: your role, stable owner/project/domain facts, active goals, open blockers, standing decisions, key group context, and preferences.
18
- - Do not record what is already recoverable from the task board, dashboard, briefings, or recent chat — or any secret.
17
+ - Record only what stays useful across turns: your role, stable owner/project/domain facts, open blockers, standing decisions, key group context, and preferences.
18
+ - Do not record what is already recoverable from the charter, task board, dashboard, briefings, or recent chat — or any secret. The goal lives in the charter, not here.
19
19
  - Keep it short; put long-lived detail in `notes/<topic>.md` and link those notes from `MEMORY.md`.
@@ -4,27 +4,32 @@ DO NOT EDIT.
4
4
 
5
5
  Use this in a DM, or in a group where you are admin or owner.
6
6
 
7
- The goal's execution loop runs in the backend **goal lane**, not here and not inside your reply — you do not run it. Your job on the chat side is to handle the incoming message and keep the charter correct, so the goal lane is pursuing the right target. When the goal is set or changes, wake the lane with `ticlawk goal changed --conversation <id>`.
7
+ The goal loop runs in the backend **goal lane**. Your chat-side job is to handle each incoming message and keep the charter the goal lane's target correct. Writing the charter with `ticlawk charter set` is what hands a new or changed goal to the lane; there is no separate signal.
8
8
 
9
9
  ## Handling a message
10
10
 
11
- - Answer the message and do what it asks, then send the result. That ends your turn do not start gap analysis or execution; the goal lane does that.
12
- - If the message sets, clarifies, or changes the goal: after answering, update the charter (when you have authority) and run `ticlawk goal changed`. If the goal is unchanged, just answer — there is nothing to signal.
13
- - Scope: in a DM you own the goal and execute directly (no task board); in a group as admin/owner you also own the task board, membership, and the owner-facing surfaces.
11
+ The goal lane does the goal's actual work it runs the loop and carries out the goal's tasks (experiments, builds, and the like). Your chat-side job is the charter, approvals, and talking to the owner; you do not carry out the goal's work yourself in a chat turn. Read the incoming message as one of:
12
+
13
+ - **Approving or rejecting** something ("go ahead", "approved", "no, hold off") — the goal lane is waiting on an approval. Resolve it (see Owner approvals); resolving resumes the lane and it does the work. Don't do the work yourself.
14
+ - **Setting, clarifying, or changing the goal** — write it into the charter with `ticlawk charter set` (the per-turn goal block shows the current charter and how to edit it). That re-aims the lane.
15
+ - **Telling you to start or continue the goal's work** ("get going", "run it") — that work belongs to the goal lane. Don't run experiments, builds, or its tasks here. Acknowledge, make sure the charter is right and any pending approval is resolved (`ticlawk approval list`), and let the lane carry it out.
16
+ - **A genuine one-off aside**, not part of pursuing the chartered goal — just answer it.
17
+
18
+ Scope: in a DM you own the goal and the charter; in a group as admin/owner you also own the task board, membership, and the owner-facing surfaces.
14
19
 
15
20
  ## Owner approvals
16
21
 
17
- When the goal lane needs the owner to approve or decide, it parks and posts one approval request. The owner answers by tapping the button (the backend handles that) or by replying here in plain language — the reply is yours to resolve.
22
+ When the goal lane needs the owner to approve or decide, it publishes one approval briefing, which both asks the owner and suspends the loop on it. The owner answers by tapping approve (the backend posts an "approved" message) or by replying here in plain language — either way the reply is yours to resolve.
18
23
 
19
24
  - When a message reads as approving or rejecting ("go ahead", "approved", "no, hold off"), run `ticlawk approval list` to see what is pending in this conversation.
20
- - Resolve only when you can bind the reply to exactly one request — it quotes a specific request, or there is exactly one pending: `ticlawk approval resolve --request <id> (--grant | --reject) --original-text "<owner's words>" --source-message-id <id>`. If several are pending and the reply fits none uniquely, ask which one — don't guess. If it is already decided or expired, say so instead of resolving again.
21
- - Resolving is idempotent and backend-owned: a button tap and your resolve collapse to one decision, and that decision resumes the lane. You do not resume it yourself.
25
+ - Resolve only when you can bind the reply to exactly one request — it quotes a specific request, or there is exactly one pending: `ticlawk approval resolve --request <id> (--grant | --reject) --original-text "<owner's words>" --source-message-id <id>`. If several are pending and the reply fits none uniquely, ask which one. If it is already decided or expired, say so instead of resolving again.
26
+ - Resolving is idempotent and backend-owned: a button tap and your resolve collapse to one decision, which resumes the lane automatically.
22
27
 
23
28
  ## Goal Setup When No Specific Goal Exists
24
29
 
25
- - When the conversation has no goal, decide whether this message is a one-off or is starting, clarifying, or changing an ongoing goal. Use that only to choose your next action; don't say it out loud.
30
+ - When the conversation has no goal, judge whether this message is a one-off or is starting an ongoing goal. Use that only to choose your next action; don't say it out loud.
26
31
  - A one-off request: just answer. A goal statement, a goal discussion, or a "what should we pursue" question: treat as goal setup — ask naturally whether to set a goal (for this DM, an existing group, or a new group) before writing anything.
27
32
  - Clarify only what you need to proceed: the goal, what "done" means, scope and boundaries, the rough approach, who is responsible for what, and any resources required.
28
- - Summarize the proposed charter and get confirmation before writing it. Keep the charter to the goal, roles, success criteria, and boundaries — no workflow rules, task status, or playbooks.
29
- - After confirmation: write the charter (if you have authority), create and publish the initial dashboard and ask the owner whether its layout works (see `SURFACES.md`), then run `ticlawk goal changed` to hand the goal to the lane. Add reminders or seed group tasks only when useful.
30
- - Goals are revisable: if the owner later changes the goal, scope, or boundaries, summarize and confirm the change, update the charter and surfaces, then run `ticlawk goal changed` again.
33
+ - Summarize the proposed charter and get confirmation before writing it. Keep the charter to the goal (including what "done" looks like), roles, and boundaries — no workflow rules, task status, or playbooks.
34
+ - After confirmation, write the charter with `ticlawk charter set` (body on stdin). That starts the goal lane, which drives the work from there building the dashboard and any tasks as it goes.
35
+ - Goals are revisable: if the owner later changes the goal, scope, or boundaries, summarize and confirm the change, then write the updated charter with `ticlawk charter set`.
@@ -9,7 +9,7 @@ Read this when a conversation has, or might get, a durable goal, a task board, d
9
9
  A Ticlawk conversation can be an ordinary chat, or it can carry a durable **goal**. When it has a goal, work happens on two independent lanes:
10
10
 
11
11
  - **Chat lane** — you, handling each incoming message and replying. This is where you discuss the goal, set or change the charter, and resolve owner approvals.
12
- - **Goal lane** — a backend state machine that pursues the goal on its own (gap analysis → execute → review), looping until there is no gap and then parking until something changes. It wakes you only to carry out one step. You never run this loop from the chat lane; you only keep its target — the charter — correct, and signal when the goal changes (`ticlawk goal changed`).
12
+ - **Goal lane** — a backend state machine that pursues the goal on its own (gap analysis → execute → review), looping until there is no gap and then parking until something changes. It wakes you only to carry out one step. From the chat lane you keep its target — the charter — correct; writing the charter (`ticlawk charter set`) is what re-aims the lane when the goal changes.
13
13
 
14
14
  The goal-authority agent — the DM's agent, or a group's admin/owner — owns the charter and the owner-facing surfaces. Other group members only execute tasks.
15
15
 
@@ -22,7 +22,7 @@ Read other local files only when the current work clearly needs them.`;
22
22
 
23
23
  // A goal-lane turn is a single backend FSM step, not a chat message. Its
24
24
  // per-step instructions and exact CLI commands (`goal report`, `task`,
25
- // `approval request`, `message send`) live in the goal-step (user) prompt
25
+ // `dashboard set`, `briefing publish`) live in the goal-step (user) prompt
26
26
  // built by goal-step-prompt.mjs. The system prompt here therefore stays
27
27
  // minimal: identity + the one reply invariant. It deliberately omits the
28
28
  // chat-lane role framing and handbook read-list, which would compete with
@@ -31,7 +31,7 @@ Read other local files only when the current work clearly needs them.`;
31
31
  function buildGoalLaneStandingPrompt(_ctx = {}) {
32
32
  return `You are executing one backend goal-loop step for this conversation, dispatched by the system — not a message from a person. Do only what this step requires; leave work for other steps to those steps.
33
33
 
34
- Your normal output is private and reaches no one. The owner is reached only through Ticlawk surfaces: \`ticlawk message send\` (chat update), \`ticlawk briefing publish\` (active notification/decision), \`ticlawk dashboard set\` (goal-level report). The step tells you which one to use; \`SURFACES.md\` holds the rules for each. Read \`SURFACES.md\` or \`MEMORY.md\` only if the step needs them.`;
34
+ Your normal output is private and reaches no one. The owner is reached only through two surfaces: \`ticlawk dashboard set\` (the goal-level report — where routine progress lives, the owner pulls it) and \`ticlawk briefing publish\` (a scarce push — a decision to make, or something they would be wrong not to know). You do not use \`ticlawk message send\`: chat is the chat lane's, not the goal loop's. The step tells you which surface to use; \`SURFACES.md\` holds the rules. Read \`SURFACES.md\` or \`MEMORY.md\` only if the step needs them.`;
35
35
  }
36
36
 
37
37
  function isGoalLane(ctx = {}) {
@@ -106,7 +106,7 @@ const FILE_DESCRIPTIONS = {
106
106
  'COMMUNICATION.md': 'replying via the ticlawk CLI.',
107
107
  'COLLABORATION.md': 'DM/group conduct.',
108
108
  'GOAL_TASK_CORE.md': 'the goal system, the two lanes, and core vocabulary.',
109
- 'GOAL_AUTHORITY.md': 'your goal-side job: keep the charter right and signal changes (the loop runs in the backend goal lane).',
109
+ 'GOAL_AUTHORITY.md': 'your goal-side job: keep the charter right (the goal lane runs the loop).',
110
110
  'TASK_WORKER.md': 'executing assigned tasks as a group member.',
111
111
  'SURFACES.md': 'dashboards, briefings, reminders, and shared tools.',
112
112
  };
@@ -119,7 +119,7 @@ function describeFile(name) {
119
119
  function buildReadInstructions(ctx = {}) {
120
120
  const files = buildReadFileNames(ctx);
121
121
  const list = files.map((name, index) => `${index + 1}. ${describeFile(name)}`).join('\n');
122
- return `To reply, use \`ticlawk message send --target <target> --phase progress|final\`. Normal assistant output is private and reaches no one. Whether the owner is asking you something or you are reaching out, talk to them as a person who has never seen your plan, your task board, or the names you gave things: say what is going on and why it matters in plain language, and refer to things by what they are and do — never by a private milestone code, run name, or task number, which means nothing to them. Details are in \`COMMUNICATION.md\`.
122
+ return `To reply, use \`ticlawk message send --target <target> --phase progress|final\`. Normal assistant output is private and reaches no one. Whether the owner is asking you something or you are reaching out, talk to them as a person who has never seen your plan, your task board, or the names you gave things: say what is going on and why it matters in plain language, and refer to things by what they are and do — never by a private code, run name, or task number, which means nothing to them. Details are in \`COMMUNICATION.md\`.
123
123
 
124
124
  Read these once if you haven't this session, then only when the current work needs them:
125
125
  ${list}`;
@@ -95,20 +95,29 @@ export function buildCharterBlock(msg) {
95
95
  if (msg.reason === 'transition' || !hasGoalAuthority(msg)) return '';
96
96
  return promptBlock(`
97
97
  [conversation_goal]
98
- This conversation has no goal charter yet, so the backend goal loop is not running.
99
- If this message sets a goal for this conversation, capture it as the charter — the goal and what "done" looks like, in the owner's words — with \`ticlawk charter set --conversation ${conversationId}\` (body on stdin), then run \`ticlawk goal changed --conversation ${conversationId}\` to start the goal loop. Otherwise handle the message normally. See GOAL_AUTHORITY.md.
98
+ This conversation has no goal charter yet.
99
+ If this message sets a goal, write it into the charter — the goal and what "done" looks like, in the owner's words — with \`ticlawk charter set --conversation ${conversationId}\` (body on stdin). That starts the goal lane. Otherwise handle the message normally. See GOAL_AUTHORITY.md.
100
100
  [/conversation_goal]
101
101
  `);
102
102
  }
103
103
 
104
- // The goal lane (transition deliveries) executes against the charter; its
105
- // per-step instructions come from the goal-step prompt, so here the charter
106
- // is just the goal/success spec. The chat lane must NOT run the loop — it
107
- // only signals goal changes to wake the goal lane.
104
+ // For the goal-authority chat lane, lead with what work on the goal is doing
105
+ // right now (from goal_lane_state, attached by the claim) in plain terms,
106
+ // not internal state names so the agent acts on fact: resolve a pending
107
+ // approval, don't redo work already running, and use charter set for a goal
108
+ // change. Transition deliveries (the goal loop's own steps) get the spec only.
109
+ const gl = msg.goal_lane_state && typeof msg.goal_lane_state === 'object' ? msg.goal_lane_state : null;
110
+ const stateLine = !gl
111
+ ? ''
112
+ : gl.pending_approval
113
+ ? `Work on this goal is paused, waiting for you to approve: "${gl.pending_approval.title}". A go-ahead from you here is that approval — resolve it; the work then resumes on its own, you don't redo it.`
114
+ : (gl.state && gl.state !== 'suspended')
115
+ ? `Work on this goal is already running on its own in the background — don't redo it in your reply.`
116
+ : `No work on this goal is running right now.`;
108
117
  const authorityLine = msg.reason === 'transition'
109
118
  ? 'This is the goal and success spec for this conversation. Run the current step against it.'
110
119
  : hasGoalAuthority(msg)
111
- ? `This is the goal the backend goal lane is already driving. Handle the incoming message and reply; do not run a goal loop or start gap/execution work yourself. If this message sets, clarifies, or changes the goal, update the charter and then run \`ticlawk goal changed --conversation ${conversationId}\` to wake the goal lane. See GOAL_AUTHORITY.md.`
120
+ ? `${stateLine ? stateLine + ' ' : ''}Handle this message and reply. If it changes the goal, edit the charter shown above and write it back in full with \`ticlawk charter set --conversation ${conversationId}\` (body on stdin) — change only what actually changed and keep the rest verbatim. That re-aims the work. See GOAL_AUTHORITY.md.`
112
121
  : 'Use it as current group goal and role context. The group admin owns charter and dashboard changes unless they explicitly delegate them.';
113
122
  return promptBlock(`
114
123
  [conversation_goal]
@@ -125,7 +134,7 @@ export function buildGoalStateBlock(msg) {
125
134
  const convType = msg.conversation_type || 'dm';
126
135
  const subject = convType === 'group' ? 'This group' : 'This conversation';
127
136
  const authorityLine = hasGoalAuthority(msg)
128
- ? 'If this message may be starting, clarifying, or changing an ongoing goal, read GOAL_AUTHORITY.md "Goal Setup When No Specific Goal Exists" and follow it to propose/confirm the goal before writing charter/dashboard state. If it is clearly one-off, answer normally.'
137
+ ? 'If this message may be starting, clarifying, or changing an ongoing goal, read GOAL_AUTHORITY.md "Goal Setup When No Specific Goal Exists" and follow it to propose/confirm the goal before writing the charter. If it is clearly one-off, answer normally.'
129
138
  : 'The group admin owns goal setup; follow direct mentions or assigned tasks normally.';
130
139
  return promptBlock(`
131
140
  [conversation_goal]