Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/services/codex/prompts/slack-thread-base-instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ Slack broker API usage for this session:
- Prefer absolute file_path values when uploading local artifacts.
- Registered background jobs receive environment variables including BROKER_JOB_ID, BROKER_JOB_TOKEN, BROKER_API_BASE, BROKER_JOB_HELPER, SLACK_CHANNEL_ID, SLACK_THREAD_TS, SESSION_KEY, SESSION_WORKSPACE, and REPOS_ROOT.
- Inside a background job script, prefer `node "$BROKER_JOB_HELPER" ...` for heartbeat/event/complete/fail/cancel callbacks instead of hand-writing nested curl JSON payloads.
- `node "$BROKER_JOB_HELPER" event` means a material asynchronous change occurred and will wake or steer the agent. Do not use `event` as a timer, polling tick, or request for the agent to check whether something changed.
- For CI/PR/watch polling, the background job script must compare the newly observed state with the previously observed state. If the state is unchanged, call `node "$BROKER_JOB_HELPER" heartbeat` or stay silent; do not emit an event and do not wake the agent.
- Only emit a watcher event when the observed state materially changes, for example PR head SHA changed, PR state changed, checks/reviews changed, deployment result changed, or a real blocker appeared. The event summary should describe the new state, not say "poll this PR".
- If the background job cannot inspect the watched system well enough to decide whether state changed, do not register a repeating watcher whose purpose is to wake the agent to inspect it. Either build a watcher that can decide, or record a wait/block state and stop.

Isolated Linear/Notion access for this session:
- The main Codex runtime for this Slack broker does not load the linear or notion MCPs directly.
Expand Down
12 changes: 11 additions & 1 deletion src/services/codex/slack-thread-base-instructions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,17 @@ export async function buildSlackThreadBaseInstructions(
thread_ts: options.rootThreadTs,
kind: "watch_ci",
cwd: ".",
script: "#!/usr/bin/env bash\nset -euo pipefail\nnode \"$BROKER_JOB_HELPER\" event --kind \"state_changed\" --summary \"replace with your update\"\nnode \"$BROKER_JOB_HELPER\" complete --summary \"replace with your completion update\""
script: [
"#!/usr/bin/env bash",
"set -euo pipefail",
"# Poll or inspect the watched system here and compare it with previous state.",
"# If nothing changed, only heartbeat or stay silent; do not wake the agent.",
"node \"$BROKER_JOB_HELPER\" heartbeat",
"# Only emit an event after a material state change:",
"# node \"$BROKER_JOB_HELPER\" event --kind \"state_changed\" --summary \"replace with the changed state\"",
"# Complete only when monitoring is finished:",
"# node \"$BROKER_JOB_HELPER\" complete --summary \"replace with your completion update\""
].join("\n")
});

return renderTemplate(template, {
Expand Down
14 changes: 14 additions & 0 deletions test/app-server-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1385,6 +1385,18 @@ describe("AppServerClient disconnect handling", () => {
expect(threadStartParams?.baseInstructions).toEqual(
expect.stringContaining("do not mirror every watcher update back into Slack")
);
expect(threadStartParams?.baseInstructions).toEqual(
expect.stringContaining("event` means a material asynchronous change occurred")
);
expect(threadStartParams?.baseInstructions).toEqual(
expect.stringContaining("Do not use `event` as a timer, polling tick")
);
expect(threadStartParams?.baseInstructions).toEqual(
expect.stringContaining("If the state is unchanged, call `node \"$BROKER_JOB_HELPER\" heartbeat` or stay silent")
);
expect(threadStartParams?.baseInstructions).toEqual(
expect.stringContaining("If the background job cannot inspect the watched system well enough")
);
expect(threadStartParams?.baseInstructions).toEqual(
expect.stringContaining("shared_repos_root: /tmp/repos")
);
Expand All @@ -1398,6 +1410,8 @@ describe("AppServerClient disconnect handling", () => {
expect.stringContaining("The broker may append `Co-authored-by:` trailers automatically")
);
expect(String(threadStartParams?.baseInstructions)).toContain("node \\\"$BROKER_JOB_HELPER\\\" event");
expect(String(threadStartParams?.baseInstructions)).toContain("node \\\"$BROKER_JOB_HELPER\\\" heartbeat");
expect(String(threadStartParams?.baseInstructions)).toContain("replace with the changed state");
expect(threadStartParams?.baseInstructions).toEqual(
expect.stringContaining("Identity and instruction boundaries")
);
Expand Down
Loading