Skip to content
Merged
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
1 change: 1 addition & 0 deletions cloudflare-gastown/container/plugin/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ export class MayorGastownClient {
title: string;
body?: string;
metadata?: Record<string, unknown>;
labels?: string[];
}): Promise<SlingResult> {
return this.request<SlingResult>(this.mayorPath('/sling'), {
method: 'POST',
Expand Down
5 changes: 5 additions & 0 deletions cloudflare-gastown/container/plugin/mayor-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ export function createMayorTools(client: MayorGastownClient) {
.string()
.describe('JSON-encoded metadata object for additional context')
.optional(),
labels: tool.schema
.array(tool.schema.string())
.describe('Labels to attach to the bead (e.g. ["gt:pr-fixup"])')
.optional(),
},
async execute(args) {
const metadata = args.metadata ? parseJsonObject(args.metadata, 'metadata') : undefined;
Expand All @@ -75,6 +79,7 @@ export function createMayorTools(client: MayorGastownClient) {
title: args.title,
body: args.body,
metadata,
labels: args.labels,
});
return [
`Task slung successfully.`,
Expand Down
6 changes: 6 additions & 0 deletions cloudflare-gastown/docs/local-debug-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,7 @@ grep "container\|startAgent\|eviction" /tmp/gastown-wrangler.log | tail -20
### Drain Flag Clearing

The TownDO's `_draining` flag is cleared by whichever happens first:

- **Heartbeat instance ID change** (~30s): each container has a UUID. When a new container's heartbeat arrives with a different ID, the drain clears.
- **Nonce handshake**: the new container calls `/container-ready` with the drain nonce.
- **Hard timeout** (7 min): safety net if no heartbeat or handshake arrives.
Expand Down Expand Up @@ -276,28 +277,33 @@ docker kill $(docker ps -q) 2>/dev/null
### Drain Stuck "Waiting for N agents"

Agents show as `running` but aren't doing work. Common causes:

- **`fetchPendingNudges` hanging**: should be skipped during drain (check `_draining` flag)
- **`server.heartbeat` clearing idle timer**: these events should be in `IDLE_TIMER_IGNORE_EVENTS`
- **Agent in `starting` status**: `session.prompt()` blocking. Status is set to `running` before the prompt now.

### Drain Flag Persists After Restart

The drain banner stays visible after the container restarted. Causes:

- **No heartbeats arriving**: container failed to start, no agents registered
- **`_containerInstanceId` not persisted**: should be in `ctx.storage`
- Fallback: wait for the 7-minute hard timeout

### Git Credential Errors

Container starts but agents fail at `git clone`:

```
Error checking if container is ready: Invalid username
```

This means the git token is stale/expired. Refresh credentials in the town settings.

### Accumulating Escalation Beads

Triage/escalation beads pile up with `rig_id=NULL`. These are by design:

- `type=escalation` beads surface for human attention (merge conflicts, rework)
- `type=issue` triage beads are handled by `maybeDispatchTriageAgent`
- GUPP force-stop beads are created by the patrol system for stuck agents
Expand Down
2 changes: 2 additions & 0 deletions cloudflare-gastown/src/dos/Town.do.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2074,6 +2074,7 @@ export class TownDO extends DurableObject<Env> {
body?: string;
priority?: string;
metadata?: Record<string, unknown>;
labels?: string[];
}): Promise<{ bead: Bead; agent: Agent }> {
const createdBead = beadOps.createBead(this.sql, {
type: 'issue',
Expand All @@ -2082,6 +2083,7 @@ export class TownDO extends DurableObject<Env> {
priority: BeadPriority.catch('medium').parse(input.priority ?? 'medium'),
rig_id: input.rigId,
metadata: input.metadata,
labels: input.labels,
});

events.insertEvent(this.sql, 'bead_created', {
Expand Down
12 changes: 12 additions & 0 deletions cloudflare-gastown/src/dos/town/agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -493,12 +493,24 @@ export function prime(sql: SqlStorage, agentId: string): PrimeContext {
};
}

// Build PR fixup context if the hooked bead is a PR fixup request
let pr_fixup_context: PrimeContext['pr_fixup_context'] = null;
if (hookedBead?.labels.includes('gt:pr-fixup') && hookedBead.metadata) {
const meta = hookedBead.metadata as Record<string, unknown>;
pr_fixup_context = {
pr_url: typeof meta.pr_url === 'string' ? meta.pr_url : null,
branch: typeof meta.branch === 'string' ? meta.branch : null,
target_branch: typeof meta.target_branch === 'string' ? meta.target_branch : null,
};
}

return {
agent,
hooked_bead: hookedBead,
undelivered_mail: undeliveredMail,
open_beads: openBeads,
rework_context,
pr_fixup_context,
};
}

Expand Down
11 changes: 11 additions & 0 deletions cloudflare-gastown/src/dos/town/review-queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,17 @@ export function agentDone(sql: SqlStorage, agentId: string, input: AgentDoneInpu
return;
}

// PR-fixup beads skip the review queue. The polecat pushed fixup commits
// to an existing PR branch — no separate review is needed.
if (hookedBead?.labels.includes('gt:pr-fixup')) {
Comment thread
jrf0110 marked this conversation as resolved.
console.log(
`[review-queue] agentDone: pr-fixup bead ${agent.current_hook_bead_id} — closing directly (skip review)`
);
closeBead(sql, agent.current_hook_bead_id, agentId);
unhookBead(sql, agentId);
return;
}

if (agent.role === 'refinery') {
// The refinery handles merging (direct strategy) or PR creation (pr strategy)
// itself. When it calls gt_done:
Expand Down
1 change: 1 addition & 0 deletions cloudflare-gastown/src/handlers/mayor-tools.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const MayorSlingBody = z.object({
title: z.string().min(1),
body: z.string().optional(),
metadata: z.record(z.string(), z.unknown()).optional(),
labels: z.array(z.string()).optional(),
});

const MayorSlingBatchBody = z
Expand Down
6 changes: 6 additions & 0 deletions cloudflare-gastown/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,12 @@ export type PrimeContext = {
original_bead_title: string | null;
mr_bead_id: string | null;
} | null;
/** Present when the hooked bead is a PR fixup (gt:pr-fixup label). */
pr_fixup_context: {
pr_url: string | null;
branch: string | null;
target_branch: string | null;
} | null;
};

// -- Agent done --
Expand Down