chore: update orchestrator/chat skills and dev tooling

- Orchestrator skill: add Claude vs Codex guidelines, structured audit
  patterns, narrow single-purpose review passes
- Chat skill: improved coordination helpers
- Dev script and config tweaks
This commit is contained in:
Mohamed Boudra
2026-03-24 21:22:14 +07:00
parent a54687d3ef
commit fa45ab593b
7 changed files with 372 additions and 80 deletions

View File

@@ -32,6 +32,7 @@ Do not read or write the backing files directly. Use `chat.sh`.
Mentions and replies are active:
- if you post `@agent-id` in a message, chat will notify that agent immediately
- if you post `@everyone`, chat will notify every agent that has already participated in that room, as if you had tagged each of them directly
- if you use `--reply-to`, chat will notify the author of the replied-to message immediately
- notifications are sent with `paseo send --no-wait`
@@ -88,6 +89,14 @@ skills/paseo-chat/bin/chat.sh post \
--body "@agent-beta can you verify the relay path next?"
```
Notify the whole room:
```bash
skills/paseo-chat/bin/chat.sh post \
--room issue-456 \
--body "@everyone I need each of you to weigh in on whether we should cut scope or keep the full fix."
```
### Read recent messages
```bash
@@ -113,6 +122,8 @@ When using a room:
- post updates when they would help another agent or your future self
- use `--reply-to` when responding to a specific message
- use `@agent-id` when you want to get a specific agent's attention
- use `@everyone` sparingly when you want all prior participants in the room to respond or re-check the thread
- prefer `@agent-id` most of the time so you only interrupt the agents who actually need the message
- check chat frequently enough that shared coordination actually works
Typical things to post:
@@ -122,6 +133,15 @@ Typical things to post:
- review findings
- important context and memories another agent may need later
Good use of `@everyone`:
- you need opinions or explicit responses from the whole team
- the room has shared context and each participant should re-check it now
Avoid `@everyone` when:
- only one or two agents need to act
- different agents are doing different jobs and you do not want to interrupt all of them
- a normal post is enough because no immediate response is needed
## Your Job
1. Understand whether you should use an existing room or create a new one
@@ -131,3 +151,4 @@ Typical things to post:
5. Post clearly
6. Use `--reply-to` when replying to a specific message
7. Use `@agent-id` when you want to notify someone directly
8. Use `@everyone` only when you intentionally want to notify the full room and prompt a broad response

View File

@@ -9,6 +9,7 @@ Usage:
chat.sh room show --room ROOM
chat.sh post --room ROOM (--body TEXT | --body-file PATH) [--agent-id ID] [--agent-name NAME] [--reply-to MESSAGE_ID]
chat.sh read --room ROOM [--limit N] [--since ISO_TIMESTAMP] [--agent-id ID]
chat.sh wait --room ROOM [--since MESSAGE_ID] [--timeout SECONDS]
Options:
--root PATH Override chat root (default: ~/.paseo/chat)
@@ -82,26 +83,8 @@ frontmatter_value() {
' "$file"
}
print_message() {
message_body() {
local file="$1"
local created_at agent_id agent_name reply_to message_id
created_at="$(frontmatter_value "created_at" "$file")"
agent_id="$(frontmatter_value "agent_id" "$file")"
agent_name="$(frontmatter_value "agent_name" "$file")"
reply_to="$(frontmatter_value "reply_to" "$file")"
message_id="$(frontmatter_value "message_id" "$file")"
printf '### %s · %s' "$created_at" "$agent_id"
if [[ -n "$agent_name" ]]; then
printf ' (%s)' "$agent_name"
fi
printf '\n'
printf 'message_id: %s\n' "$message_id"
if [[ -n "$reply_to" ]]; then
printf 'reply_to: %s\n' "$reply_to"
fi
printf '\n'
awk '
BEGIN { delim_count = 0 }
/^---$/ {
@@ -112,6 +95,48 @@ print_message() {
print
}
' "$file"
}
message_preview() {
local file="$1"
local max_length="${2:-100}"
local preview
preview="$(
message_body "$file" |
awk 'NF { print; exit }' |
tr -s '[:space:]' ' ' |
sed 's/^ //; s/ $//'
)"
if [[ ${#preview} -gt "$max_length" ]]; then
preview="${preview:0:$((max_length - 3))}..."
fi
printf '%s' "$preview"
}
print_message() {
local file="$1"
local created_at agent_id agent_name reply_to message_id
created_at="$(frontmatter_value "created_at" "$file")"
agent_id="$(frontmatter_value "agent_id" "$file")"
agent_name="$(frontmatter_value "agent_name" "$file")"
reply_to="$(frontmatter_value "reply_to" "$file")"
message_id="$(frontmatter_value "message_id" "$file")"
if [[ -n "$agent_name" ]]; then
printf '### %s · %s (%s)\n' "$created_at" "$agent_name" "$agent_id"
else
printf '### %s · %s\n' "$created_at" "$agent_id"
fi
printf 'message_id: %s\n' "$message_id"
if [[ -n "$reply_to" ]]; then
printf 'reply_to: %s\n' "$reply_to"
fi
printf '\n'
message_body "$file"
printf '\n'
}
@@ -133,11 +158,37 @@ find_message_file_by_id() {
done
}
message_files_sorted() {
local room="$1"
find "$rooms_root/$room/messages" -name '*.md' -type f | sort
}
print_message_files() {
local files=("$@")
local index
for ((index = 0; index < ${#files[@]}; index += 1)); do
print_message "${files[$index]}"
if [[ $index -lt $((${#files[@]} - 1)) ]]; then
printf -- '---\n\n'
fi
done
}
extract_mentions() {
local body="$1"
printf '%s\n' "$body" | grep -oE '@[A-Za-z0-9._:-]+' | sed 's/^@//' | awk '!seen[$0]++'
}
room_participant_ids() {
local room="$1"
find "$rooms_root/$room/messages" -name '*.md' -type f | sort |
while IFS= read -r file; do
frontmatter_value "agent_id" "$file"
done |
awk 'NF && $0 != "manual" && !seen[$0]++'
}
notify_agent() {
local target_agent_id="$1"
local room="$2"
@@ -145,25 +196,39 @@ notify_agent() {
local sender_agent_name="$4"
local message_id="$5"
local reason="$6"
local message_file="$7"
if [[ -z "$target_agent_id" || "$target_agent_id" == "manual" || "$target_agent_id" == "$sender_agent_id" ]]; then
return
fi
local sender_label="$sender_agent_id"
local sender_label
if [[ -n "$sender_agent_name" ]]; then
sender_label="$sender_agent_name ($sender_agent_id)"
else
sender_label="$sender_agent_id"
fi
local notification="You have a new chat message.
local notification full_message
notification="You have a new chat message.
Room: $room
From: $sender_label
Message ID: $message_id
Reason: $reason
Reason: $reason"
To read it:
skills/paseo-chat/bin/chat.sh read --room $room --limit 10"
full_message="$(message_body "$message_file")"
if [[ -n "$full_message" ]]; then
notification="${notification}
---
$full_message"
fi
notification="${notification}
-----"
"$paseo_bin" send --no-wait "$target_agent_id" "$notification" >/dev/null 2>&1 || true
}
@@ -287,6 +352,7 @@ case "$command_name" in
agent_id="${PASEO_AGENT_ID:-manual}"
agent_name=""
reply_to=""
auto_resolve_name=true
while [[ $# -gt 0 ]]; do
case "$1" in
@@ -294,7 +360,7 @@ case "$command_name" in
--body) body_input="$2"; shift 2 ;;
--body-file) body_file="$2"; shift 2 ;;
--agent-id) agent_id="$2"; shift 2 ;;
--agent-name) agent_name="$2"; shift 2 ;;
--agent-name) agent_name="$2"; auto_resolve_name=false; shift 2 ;;
--reply-to) reply_to="$2"; shift 2 ;;
*) echo "Unknown option: $1" >&2; usage ;;
esac
@@ -303,6 +369,11 @@ case "$command_name" in
[[ -n "$room" ]] || { echo "Error: --room is required" >&2; exit 1; }
sanitize_room_path "$room"
require_room "$room"
if [[ "$auto_resolve_name" == true && -z "$agent_name" && "$agent_id" != "manual" ]]; then
agent_name="$("$paseo_bin" inspect "$agent_id" --json 2>/dev/null | jq -r '.Name // empty' 2>/dev/null)" || agent_name=""
fi
body="$(load_body "$body_input" "$body_file")"
created_at="$(date -u +'%Y-%m-%dT%H:%M:%SZ')"
@@ -329,13 +400,31 @@ case "$command_name" in
reply_file="$(find_message_file_by_id "$room" "$reply_to" || true)"
if [[ -n "$reply_file" ]]; then
reply_target_agent_id="$(frontmatter_value "agent_id" "$reply_file")"
notify_agent "$reply_target_agent_id" "$room" "$agent_id" "$agent_name" "$message_id" "reply to $reply_to"
notify_agent "$reply_target_agent_id" "$room" "$agent_id" "$agent_name" "$message_id" "reply to $reply_to" "$message_file"
notified_agents+=("$reply_target_agent_id")
fi
fi
while IFS= read -r mentioned_agent_id; do
[[ -n "$mentioned_agent_id" ]] || continue
if [[ "$mentioned_agent_id" == "everyone" ]]; then
while IFS= read -r participant_agent_id; do
[[ -n "$participant_agent_id" ]] || continue
already_notified=false
for existing_id in "${notified_agents[@]}"; do
if [[ "$existing_id" == "$participant_agent_id" ]]; then
already_notified=true
break
fi
done
if [[ "$already_notified" == false ]]; then
notify_agent "$participant_agent_id" "$room" "$agent_id" "$agent_name" "$message_id" "@everyone mention" "$message_file"
notified_agents+=("$participant_agent_id")
fi
done < <(room_participant_ids "$room")
continue
fi
already_notified=false
for existing_id in "${notified_agents[@]}"; do
if [[ "$existing_id" == "$mentioned_agent_id" ]]; then
@@ -344,7 +433,7 @@ case "$command_name" in
fi
done
if [[ "$already_notified" == false ]]; then
notify_agent "$mentioned_agent_id" "$room" "$agent_id" "$agent_name" "$message_id" "direct mention"
notify_agent "$mentioned_agent_id" "$room" "$agent_id" "$agent_name" "$message_id" "direct mention" "$message_file"
notified_agents+=("$mentioned_agent_id")
fi
done < <(extract_mentions "$body")
@@ -372,8 +461,7 @@ case "$command_name" in
sanitize_room_path "$room"
require_room "$room"
messages_dir="$rooms_root/$room/messages"
mapfile -t all_files < <(find "$messages_dir" -name '*.md' -type f | sort)
mapfile -t all_files < <(message_files_sorted "$room")
filtered_files=()
for file in "${all_files[@]}"; do
@@ -401,11 +489,79 @@ case "$command_name" in
fi
fi
for ((index = start_index; index < ${#filtered_files[@]}; index += 1)); do
print_message "${filtered_files[$index]}"
if [[ $index -lt $((${#filtered_files[@]} - 1)) ]]; then
printf -- '---\n\n'
print_message_files "${filtered_files[@]:$start_index}"
;;
wait)
room=""
since_message_id=""
timeout_seconds=""
poll_interval_seconds="1"
while [[ $# -gt 0 ]]; do
case "$1" in
--room) room="$2"; shift 2 ;;
--since) since_message_id="$2"; shift 2 ;;
--timeout) timeout_seconds="$2"; shift 2 ;;
*) echo "Unknown option: $1" >&2; usage ;;
esac
done
[[ -n "$room" ]] || { echo "Error: --room is required" >&2; exit 1; }
sanitize_room_path "$room"
require_room "$room"
if [[ -n "$timeout_seconds" ]] && ! [[ "$timeout_seconds" =~ ^[0-9]+$ ]]; then
echo "Error: --timeout must be a number" >&2
exit 1
fi
baseline_file=""
if [[ -n "$since_message_id" ]]; then
baseline_file="$(find_message_file_by_id "$room" "$since_message_id" || true)"
[[ -n "$baseline_file" ]] || { echo "Error: message not found: $since_message_id" >&2; exit 1; }
else
while IFS= read -r file; do
baseline_file="$file"
done < <(message_files_sorted "$room")
fi
started_at="$(date +%s)"
trap 'exit 130' INT TERM
while true; do
mapfile -t all_files < <(message_files_sorted "$room")
new_files=()
after_baseline=false
if [[ -z "$baseline_file" ]]; then
after_baseline=true
fi
for file in "${all_files[@]}"; do
if [[ "$after_baseline" == true ]]; then
new_files+=("$file")
continue
fi
if [[ "$file" == "$baseline_file" ]]; then
after_baseline=true
fi
done
if [[ ${#new_files[@]} -gt 0 ]]; then
print_message_files "${new_files[@]}"
exit 0
fi
if [[ -n "$timeout_seconds" ]]; then
now="$(date +%s)"
if (( now - started_at >= timeout_seconds )); then
exit 124
fi
fi
sleep "$poll_interval_seconds"
done
;;

View File

@@ -69,14 +69,42 @@ Chat is not just passive storage. Mentions and replies trigger direct notificati
Do not assume chat is required for every orchestration. Use it when it adds coordination value.
## Provider Selection: Claude vs Codex
Claude and Codex have complementary strengths and weaknesses. Use each to cover the other's blind spots.
**Codex** — the implementation workhorse (`--provider codex --mode full-access`):
- Strengths: thorough, methodical, catches edge cases, good at deep implementation
- Weakness: over-engineers. Sees every edge case and hardens against each with defensive code — coordination callbacks, guard clauses, retry wrappers, extra abstractions. Adds robustness through layers instead of designing simplicity.
**Claude** — the design and review brain (`--mode bypassPermissions`):
- Strengths: good design instinct, sees the simple solution, strong at architecture and reasoning
- Weakness: can be careless. Misses details, skips steps, doesn't always verify thoroughly.
**The workflow:**
- Use **Codex for implementation** — it's the workhorse for writing code
- Use **Claude for review after Codex implements** — specifically to catch over-engineering. Ask: "Flag any defensive code, coordination layers, or abstractions that could be eliminated by a simpler design"
- Use **Codex for review after Claude implements** — specifically to catch carelessness, missed edge cases, and incomplete verification
- Each one audits the other's blind spot: Claude simplifies Codex's complexity, Codex catches Claude's carelessness
**What Codex over-engineering looks like:**
- Guard clauses and null checks for states that can't happen if the data flow is designed right
- Coordination callbacks or event bridges connecting things that shouldn't need connecting
- Helper functions wrapping trivial operations — indirection without value
- try/catch blocks duplicating error handling the caller already does
- Optional fields and union types that create ambiguity instead of clarity
- Code added "just in case" that can never execute given the current flow
The fix is never "add more defensive code." The fix is design simpler types and data flow so the edge cases can't happen. Simplicity IS robustness.
## Agent Types
Implementation and review are not the only agent roles. Use the right agent for the job:
- **Exploration agents** — Read the codebase, map dependencies, understand how something works. Launch these when you need context before making decisions.
- **Investigation agents** — Debug a problem, trace a bug, find root cause. Must NOT edit files.
- **Implementation agents** — Write code to meet acceptance criteria.
- **Review agents** — Independently verify implementation meets criteria. Must NOT edit files.
- **Implementation agents** — Write code to meet acceptance criteria. **Default to Codex.**
- **Review agents** — Independently verify implementation meets criteria. Must NOT edit files. **Use the opposite provider from whoever implemented** — Claude reviews Codex's work, Codex reviews Claude's.
- **Second opinion agents** — When you're unsure about an approach, launch an agent (different provider if possible) to evaluate the plan and poke holes in it.
Don't limit yourself to implement → review. Explore first if you need context. Get a second opinion if the design is tricky. The user is paying for thoroughness, not speed.
@@ -202,34 +230,136 @@ If agents should coordinate through chat, include:
- that they should check it very often
- that they should use `@agent-id` and replies when they want to notify someone directly
## Always Review
## Always Review — Audit Agents Are Your Eyes
After every implementation agent finishes, spin up a **review agent** to independently verify the work.
After every implementation agent finishes, spin up an **audit agent** to independently verify the work. Implementation agents will always drift — not because they're liars, but because that's how LLMs work. They sneak in workarounds, forget constraints, add defensive code, or silently skip hard parts. You cannot trust their self-assessment. Audit agents are how you actually see what happened.
The review agent should:
- Check that all acceptance criteria are met — not by reading the agent's claims, but by actually testing
- Verify tests exist and pass
- Check that the agent didn't hand-wave or work around the problem
- Flag any regressions
- Run typecheck and tests
### Audit prompts must be structured checklists, not open-ended
```bash
paseo run --provider codex --mode full-access --name "[Review] Task description" \
"Review the recent changes in [repo]. The goal was [goal].
Never say "review and let me know if you find issues." That produces vague, unfocused reports. Instead, define the **specific risks and acceptance criteria** you need verified, then ask **narrow yes/no questions**.
Acceptance criteria:
- [criterion 1]
- [criterion 2]
You know what the risks are because you wrote the acceptance criteria and you know what agents tend to get wrong. Turn that knowledge into verification questions.
Verify EACH criterion independently. Run the tests. Run typecheck.
Check that the implementation actually solves the problem — not that
it appears to solve it. Look for workarounds, hand-waves, and missing
edge cases.
DO NOT edit any files. Report your findings."
**Bad** (open-ended, unfocused):
```
Review the recent changes. Check for issues, regressions, and code quality problems.
Report your findings.
```
Don't skip this step. Don't trust the implementation agent's self-assessment.
This produces a rambling report that misses the things you actually care about.
**Good** (structured checklist with yes/no questions):
```
Verify the event stream redesign. Answer each question with YES/NO
and evidence (grep output, test output, line reference). DO NOT edit files.
## Acceptance criteria
1. Does `grep -rn "async \*stream(" providers/` return zero matches?
2. Does `grep -rn "Pushable" providers/` return zero matches?
3. Does `npm run typecheck` pass with zero errors?
4. Do all 77 manager tests pass? Run them and paste the summary.
5. Do all 8 integration tests pass against real Claude? Run them.
6. Is there any code path where events are emitted WITHOUT turnId?
grep for notifySubscribers and check each call site.
7. Are there any new helper functions or abstractions that didn't
exist before? If yes, justify each one.
8. Is there any try/catch that duplicates error handling from a caller?
## What to flag
- Any YES that should be NO, or NO that should be YES
- Any question you cannot confidently answer
```
### How to design audit questions
Your audit questions should come from three sources:
1. **Acceptance criteria** — turn each criterion into a verification question
2. **Known risks** — what does this provider (Claude/Codex) tend to get wrong? Ask about those specific patterns
3. **Constraints** — what was the agent told NOT to do? Verify they didn't do it
For Codex implementations, always ask:
- "Are there any new abstractions, helpers, or utility functions?"
- "Is there any defensive code (guard clauses, null checks, fallbacks) that could be designed away?"
- "Are there any coordination layers between components?"
For Claude implementations, always ask:
- "Did the agent skip any acceptance criteria?"
- "Are all edge cases actually tested, not just mentioned?"
- "Did the agent verify by running tests, or just claim they pass?"
### Use narrow, single-purpose audit passes
Don't run one big audit that checks everything. Run multiple focused passes, each with a single concern. This produces sharper results because the agent stays focused on one thing instead of skimming many.
The examples below are templates — every task will require different questions. Think about what specifically could go wrong for THIS task, with THIS provider, and write questions that probe those risks. The templates show the structure and tone, not the exact questions to ask.
**Over-engineering pass** (after Codex implements):
```
Answer YES/NO with line references. DO NOT edit files.
1. List every new function/method/class that didn't exist before this change.
For each one: is it necessary, or could the caller do this inline?
2. List every try/catch block in the changed code.
For each one: does the caller already handle this error?
3. List every guard clause (if !x return/throw) in the changed code.
For each one: can this state actually happen given the data flow?
4. Are there any callbacks, event bridges, or adapters connecting
two things that could talk directly?
```
**Testing pass** (after any implementation):
```
Answer YES/NO with evidence. DO NOT edit files.
1. Run the test suite. Paste the summary line. How many pass/fail/skip?
2. For each acceptance criterion, name the specific test that covers it.
If no test exists, say MISSING.
3. Are any tests using mocks where real implementations exist?
4. Are any tests trivially passing (asserting true, empty test bodies,
testing the mock instead of the code)?
5. Run each test in isolation — do any fail when run alone but pass
in the full suite? (ordering dependency)
```
**Correctness pass** (after any implementation):
```
Answer YES/NO with evidence. DO NOT edit files.
1. For each acceptance criterion, trace the code path that implements it.
Does the path actually work end-to-end?
2. Are there any TODO/FIXME/HACK comments in the changed code?
3. Does npm run typecheck pass? Paste the output.
4. Are there any type assertions (as X) or @ts-ignore in the changed code?
5. Run grep for [specific patterns that should/shouldn't exist].
```
**Cleanup pass** (after deletion/refactoring):
```
Answer YES/NO with evidence. DO NOT edit files.
1. grep for [each deleted concept]. Zero matches in production code?
2. Are there any imports of deleted modules?
3. Are there any dead code paths that reference deleted functionality?
4. Are there any comments referencing deleted concepts?
5. Does the test suite still pass after deletions?
```
Choose which passes to run based on the task and the provider that implemented it. You don't always need all of them — pick the ones that match the risks. Write fresh questions each time based on what you know about the specific task, the acceptance criteria, and what the implementation agent was likely to get wrong.
### Audit agents must run commands, not read claims
The audit agent must independently execute verification:
- Run `npm run typecheck` — don't accept "typecheck passes" from the impl agent
- Run the test suite — don't accept "all tests pass" from the impl agent
- Run `grep` to confirm deletions — don't accept "I deleted it" from the impl agent
- Read the actual diff — don't accept a summary of changes
### Use the opposite provider for audits
When Codex implements, use Claude to audit. When Claude implements, use Codex to audit. Each catches what the other misses.
Don't skip this step. Don't trust the implementation agent's self-assessment. Your audit agents are the only way you actually know what happened.
## Course-Correcting Agents