Skip to content

Commit 3a69247

Browse files
committed
Merge remote-tracking branch 'origin/main' into feat/claw-update-available-image-tag
2 parents 73aae9c + 29baf66 commit 3a69247

76 files changed

Lines changed: 19580 additions & 1709 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.test

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ STRIPE_KILOCLAW_EARLYBIRD_COUPON_ID=coupon_test_kiloclaw_earlybird
7979
STRIPE_KILOCLAW_COMMIT_PRICE_ID=price_test_kiloclaw_commit
8080
STRIPE_KILOCLAW_STANDARD_PRICE_ID=price_test_kiloclaw_standard
8181
STRIPE_KILOCLAW_STANDARD_FIRST_MONTH_COUPON_ID=coupon_test_kiloclaw_standard_first_month
82-
STRIPE_KILOCLAW_BILLING_START=
8382

8483
# Stripe publishable key for client-side 3DS authentication
8584
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_invalid_mock_key

.specs/kiloclaw-billing.md

Lines changed: 75 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
Draft -- generated from branch `jdp/kiloclaw-billing` on 2026-03-13.
66
Updated 2026-03-19 -- pricing and trial duration changes.
7+
Updated 2026-03-20 -- promotional codes and introductory pricing.
78

89
## Conventions
910

@@ -42,6 +43,22 @@ lapses, with email notifications at each stage.
4243
7. The system MUST fail with an error at checkout time if a required
4344
plan price identifier is not configured.
4445

46+
### Standard Plan Introductory Pricing
47+
48+
1. New standard plan subscribers who do not have a prior canceled paid
49+
subscription MUST receive an introductory price for their first
50+
billing period. A canceled trial does not count as a prior paid
51+
subscription. Returning subscribers with a previously canceled paid
52+
subscription MUST receive the regular standard price.
53+
2. The system MUST automatically transition introductory-price
54+
subscribers to the regular standard price at the end of the
55+
introductory billing period.
56+
3. The automatic price transition MUST be transparent to the user: it
57+
MUST NOT appear as a pending plan switch in billing status and MUST
58+
NOT prevent the user from initiating a plan switch or canceling.
59+
4. Failure to set up the automatic price transition during subscription
60+
creation MUST NOT block checkout completion.
61+
4562
### Trial Eligibility and Creation
4663

4764
1. A trial MUST only be created automatically when a user provisions an
@@ -81,21 +98,18 @@ lapses, with email notifications at each stage.
8198
2. The system MUST allow checkout when the existing subscription status
8299
is trialing or canceled.
83100
3. The system MUST verify with the payment provider that no subscription
84-
in active or trialing (delayed-billing) status already exists for the
85-
customer before creating a new checkout session, to guard against
86-
concurrent checkouts. This check does not cover provider-side
87-
subscriptions in past-due status.
88-
4. The system MUST NOT allow promotional codes for either plan.
89-
5. The system MUST apply a provider-configured first-month discount
90-
coupon when creating a standard plan checkout session.
91-
6. When a configurable billing start date is set and is in the future,
92-
the system MUST create the subscription with a delayed billing period
93-
that begins on that date.
94-
7. When the billing start date is unset or is in the past, the system
95-
MUST start billing immediately with no delayed period.
96-
8. The system SHOULD include referral tracking data in checkout sessions
101+
in active or trialing status already exists for the customer before
102+
creating a new checkout session, to guard against concurrent
103+
checkouts. This check does not cover provider-side subscriptions in
104+
past-due status.
105+
4. The system MUST allow promotional codes on checkout for both plans.
106+
5. For standard plan checkout, the system MUST use the introductory
107+
price when the user has no prior canceled paid subscription, and the
108+
regular price when the user has a previously canceled paid
109+
subscription (see Standard Plan Introductory Pricing).
110+
6. The system SHOULD include referral tracking data in checkout sessions
97111
when a referral cookie is present.
98-
9. The system SHOULD attempt to expire open checkout sessions tagged as
112+
7. The system SHOULD attempt to expire open checkout sessions tagged as
99113
KiloClaw before creating a new checkout session, so users who
100114
abandoned a previous checkout can start fresh. Expiration is
101115
best-effort: errors from the payment provider (e.g. the session was
@@ -111,9 +125,8 @@ lapses, with email notifications at each stage.
111125
the subscription to the standard plan.
112126
2. When a commit subscription is created, the system MUST record a
113127
commit-period end date six calendar months from the billing start.
114-
When a delayed-billing period is configured, the six months MUST
115-
start from the delayed-billing end date, not from subscription
116-
creation.
128+
For pre-launch subscriptions that had a delayed-billing trial_end,
129+
the six months starts from that trial boundary.
117130
3. When a subscription update is received and the commit-period end
118131
date is in the past, the system MUST extend it by six calendar
119132
months from the previous boundary, keeping the subscription on the
@@ -145,21 +158,35 @@ lapses, with email notifications at each stage.
145158
the commit-period end date to six calendar months from the
146159
transition date.
147160
8. The system MUST allow cancellation of user-initiated plan switches.
161+
9. The system MUST reject a plan switch if a user-initiated plan switch
162+
is already pending.
163+
10. If an automatic price transition is pending when the user requests a
164+
plan switch, the system MUST replace the automatic transition with
165+
the user's requested switch.
166+
11. After canceling a user-initiated plan switch, if the subscription is
167+
still on the introductory price, the system MUST restore the
168+
automatic price transition. Failure to restore the transition MUST
169+
NOT prevent the switch cancellation from succeeding.
148170

149171
### Cancellation and Reactivation
150172

151173
1. The system MUST reject a cancellation request if no active
152174
subscription with a payment provider ID exists.
153175
2. The system MUST reject a cancellation request if cancellation is
154176
already pending.
155-
3. When canceling a subscription that has a pending schedule, the system
156-
MUST release the schedule before setting the cancel-at-period-end
157-
flag.
177+
3. When canceling a subscription that has a pending schedule — whether
178+
a user-initiated plan switch or an automatic price transition — the
179+
system MUST release the schedule before setting the
180+
cancel-at-period-end flag.
158181
4. Cancellation MUST NOT terminate access immediately; access MUST
159182
continue until the current billing period ends.
160183
5. The system MUST allow reactivation of a subscription that is pending
161184
cancellation.
162185
6. On reactivation, the system MUST clear the cancel-at-period-end flag.
186+
7. On reactivation, if the subscription is still on the introductory
187+
price, the system MUST restore the automatic price transition.
188+
Failure to restore the transition MUST NOT prevent the reactivation
189+
from succeeding.
163190

164191
### Billing Lifecycle Background Job
165192

@@ -169,6 +196,9 @@ lapses, with email notifications at each stage.
169196
2. Each sweep in the background job MUST process users independently;
170197
a failure for one user MUST NOT prevent processing of other users.
171198
3. All errors during sweep processing MUST be captured for monitoring.
199+
4. The background job MUST detect active subscriptions on the
200+
introductory price that have no automatic price transition pending
201+
and MUST set up the missing transition.
172202

173203
### Trial Expiry Warnings
174204

@@ -258,8 +288,9 @@ lapses, with email notifications at each stage.
258288
### Payment Provider Status Mapping
259289

260290
1. When the payment provider reports a subscription as "trialing"
261-
(delayed billing), the system MUST map this to active status
262-
internally, since delayed billing is not a product-level trial.
291+
(e.g. pre-launch delayed billing), the system MUST map this to
292+
active status internally, since delayed billing is not a
293+
product-level trial.
263294
2. When the payment provider reports "incomplete" or "paused" status,
264295
the system MUST map these to terminal statuses (unpaid or canceled
265296
respectively).
@@ -299,6 +330,28 @@ lapses, with email notifications at each stage.
299330

300331
### Changelog
301332

333+
#### 2026-03-21 -- Remove delayed-billing start date
334+
335+
- Removed configurable billing start date (`STRIPE_KILOCLAW_BILLING_START`)
336+
and associated checkout rules (former rules 6–7). New subscriptions now
337+
always bill immediately.
338+
- Pre-launch subscriptions created with a delayed `trial_end` are still
339+
handled correctly; the trialing→active status mapping remains until those
340+
subscriptions transition.
341+
342+
#### 2026-03-20 -- Promotional codes and introductory pricing
343+
344+
Previous values:
345+
346+
- Standard plan first-month discount: coupon applied at checkout
347+
- Promotional codes: not allowed on either plan
348+
349+
New values:
350+
351+
- Standard plan first-month discount: introductory price with automatic
352+
transition to regular price at period end
353+
- Promotional codes: allowed on both plans at checkout
354+
302355
#### 2026-03-19 -- Pricing and trial changes
303356

304357
Previous values:

AGENTS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,7 @@ When creating or updating a pull request, you **must** follow the PR template in
5252
- Do not leave HTML comments from the template in the final description — replace them with actual content.
5353
- PR descriptions must be accurate and valuable to reviewers. Generic or boilerplate descriptions waste reviewer time.
5454
- Review all commits on the branch (not just the latest) when writing the summary.
55+
56+
## KiloClaw Billing
57+
58+
Before making **any** changes related to KiloClaw billing — including bug fixes, new features, refactors, or reviews — you **must** first read the billing spec at `.specs/kiloclaw-billing.md`. This applies to all code paths that touch billing logic, pricing, invoicing, usage metering, or payment flows.

cloud-agent-next/wrapper/src/connection.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,28 @@ export function createConnectionManager(
273273
return false;
274274
}
275275

276+
function getTerminalErrorText(eventType: string, properties: Record<string, unknown>): string {
277+
const error = properties.error;
278+
if (typeof error === 'string') {
279+
return error;
280+
}
281+
282+
if (isRecord(error)) {
283+
if (typeof error.message === 'string') {
284+
return error.message;
285+
}
286+
287+
const data = error.data;
288+
if (isRecord(data) && typeof data.message === 'string') {
289+
return data.message;
290+
}
291+
292+
return JSON.stringify(error);
293+
}
294+
295+
return `Insufficient credits: ${eventType}`;
296+
}
297+
276298
/**
277299
* Start the SDK event subscription. Runs in the background.
278300
* Replaces the old SSE consumer with a typed event stream from the SDK.
@@ -367,7 +389,7 @@ export function createConnectionManager(
367389

368390
// Terminal error detection
369391
if (isTerminalError(eventType, properties)) {
370-
callbacks.onTerminalError(eventType);
392+
callbacks.onTerminalError(getTerminalErrorText(eventType, properties));
371393
return;
372394
}
373395

cloudflare-gastown/container/src/process-manager.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -368,12 +368,20 @@ async function handleIdleEvent(agent: ManagedAgent, onExit: () => void): Promise
368368
return;
369369
}
370370

371-
// No nudges (or fetch error) — (re)start the idle timeout
371+
// No nudges (or fetch error) — (re)start the idle timeout.
372+
// Refineries get a longer timeout because their workflow is multi-step
373+
// (diff → analyze → decide → merge/rework). The 2-min default kills the
374+
// session between LLM turns when the refinery responds with text before
375+
// issuing a tool call. See #1342.
372376
clearIdleTimer(agentId);
373377
const timeoutMs =
374-
process.env.AGENT_IDLE_TIMEOUT_MS !== undefined
375-
? Number(process.env.AGENT_IDLE_TIMEOUT_MS)
376-
: 120_000;
378+
agent.role === 'refinery'
379+
? process.env.REFINERY_IDLE_TIMEOUT_MS !== undefined
380+
? Number(process.env.REFINERY_IDLE_TIMEOUT_MS)
381+
: 600_000
382+
: process.env.AGENT_IDLE_TIMEOUT_MS !== undefined
383+
? Number(process.env.AGENT_IDLE_TIMEOUT_MS)
384+
: 120_000;
377385

378386
console.log(
379387
`${MANAGER_LOG} handleIdleEvent: no nudges for ${agentId}, idle timeout in ${timeoutMs}ms`

cloudflare-gastown/src/dos/Town.do.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1103,6 +1103,24 @@ export class TownDO extends DurableObject<Env> {
11031103
this.broadcastAgentStatus(agentId, message);
11041104
}
11051105

1106+
/** Test-only: directly set dispatch_attempts (and optionally last_activity_at) for an agent. */
1107+
async setAgentDispatchAttempts(
1108+
agentId: string,
1109+
attempts: number,
1110+
lastActivityAt?: string
1111+
): Promise<void> {
1112+
query(
1113+
this.sql,
1114+
/* sql */ `
1115+
UPDATE ${agent_metadata}
1116+
SET ${agent_metadata.columns.dispatch_attempts} = ?,
1117+
${agent_metadata.columns.last_activity_at} = COALESCE(?, ${agent_metadata.columns.last_activity_at})
1118+
WHERE ${agent_metadata.bead_id} = ?
1119+
`,
1120+
[attempts, lastActivityAt ?? null, agentId]
1121+
);
1122+
}
1123+
11061124
// ══════════════════════════════════════════════════════════════════
11071125
// Mail
11081126
// ══════════════════════════════════════════════════════════════════

cloudflare-gastown/src/dos/town/reconciler.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -969,6 +969,27 @@ export function reconcileReviewQueue(sql: SqlStorage): Action[] {
969969
for (const ref of idleRefineries) {
970970
if (!ref.current_hook_bead_id) continue;
971971

972+
// Cooldown: skip if last activity is too recent (#1342)
973+
if (!staleMs(ref.last_activity_at, DISPATCH_COOLDOWN_MS)) continue;
974+
975+
// Circuit-breaker: fail the MR bead after too many attempts (#1342)
976+
if (ref.dispatch_attempts >= MAX_DISPATCH_ATTEMPTS) {
977+
actions.push({
978+
type: 'transition_bead',
979+
bead_id: ref.current_hook_bead_id,
980+
from: null,
981+
to: 'failed',
982+
reason: 'refinery max dispatch attempts exceeded',
983+
actor: 'system',
984+
});
985+
actions.push({
986+
type: 'unhook_agent',
987+
agent_id: ref.bead_id,
988+
reason: 'max dispatch attempts',
989+
});
990+
continue;
991+
}
992+
972993
const mrRows = z
973994
.object({ status: z.string(), type: z.string(), rig_id: z.string().nullable() })
974995
.array()

cloudflare-gastown/src/dos/town/review-queue.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -722,13 +722,20 @@ export function agentCompleted(
722722
}
723723
}
724724

725-
// Mark agent idle
725+
// Mark agent idle.
726+
// For refineries, preserve dispatch_attempts so Rule 6's circuit-breaker
727+
// can track cumulative re-dispatch attempts across idle→dispatch cycles.
728+
// Resetting to 0 here was enabling infinite loops (#1342). Non-refineries
729+
// reset to 0 because they unhook above and get a fresh counter on hookBead.
726730
query(
727731
sql,
728732
/* sql */ `
729733
UPDATE ${agent_metadata}
730734
SET ${agent_metadata.columns.status} = 'idle',
731-
${agent_metadata.columns.dispatch_attempts} = 0
735+
${agent_metadata.columns.dispatch_attempts} = CASE
736+
WHEN ${agent_metadata.columns.role} = 'refinery' THEN ${agent_metadata.columns.dispatch_attempts}
737+
ELSE 0
738+
END
732739
WHERE ${agent_metadata.bead_id} = ?
733740
`,
734741
[agentId]

cloudflare-gastown/src/dos/town/scheduling.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -133,16 +133,21 @@ export async function dispatchAgent(
133133
});
134134

135135
if (started) {
136-
// Best-effort: may be dropped if I/O gate is closed
137-
query(
138-
ctx.sql,
139-
/* sql */ `
140-
UPDATE ${agent_metadata}
141-
SET ${agent_metadata.columns.dispatch_attempts} = 0
142-
WHERE ${agent_metadata.bead_id} = ?
143-
`,
144-
[agent.id]
145-
);
136+
// Reset dispatch_attempts on successful start — but NOT for refineries.
137+
// Refineries can loop (idle-timeout → re-dispatch) many times on the
138+
// same MR bead, so we keep the counter monotonically increasing until
139+
// the bead is closed or the agent hooks a new bead (#1342).
140+
if (agent.role !== 'refinery') {
141+
query(
142+
ctx.sql,
143+
/* sql */ `
144+
UPDATE ${agent_metadata}
145+
SET ${agent_metadata.columns.dispatch_attempts} = 0
146+
WHERE ${agent_metadata.bead_id} = ?
147+
`,
148+
[agent.id]
149+
);
150+
}
146151
console.log(`${LOG} dispatchAgent: started agent=${agent.name}(${agent.id})`);
147152
ctx.emitEvent({
148153
event: 'agent.spawned',

0 commit comments

Comments
 (0)