Skip to content

Commit 3dce849

Browse files
Fix str.format() KeyError in instruction templates and improve error handling
The generate_machine tool was failing silently with an opaque ' ' error because P code examples in instruction templates contained unescaped curly braces that collided with Python's str.format(). Added _safe_format() helpers that fall back to manual substitution when str.format() raises KeyError/ValueError. Also improved all exception handlers across the service layer (generation, compilation, fixer) to include the exception type name and full traceback in logs, preventing opaque error messages. Includes design doc migration from .txt to .md, post-processor enhancements, validation updates, and a verified BasicPaxos tutorial generated end-to-end via the MCP tools. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 341458c commit 3dce849

File tree

63 files changed

+3535
-1500
lines changed

Some content is hidden

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

63 files changed

+3535
-1500
lines changed

Src/PeasyAI/.cursorrules

Lines changed: 0 additions & 213 deletions
Original file line numberDiff line numberDiff line change
@@ -1,213 +0,0 @@
1-
# P Language Programming Rules for Cursor
2-
3-
## About P
4-
5-
P is a state machine-based language for modeling and verifying concurrent systems. It compiles to C# and can be model-checked using PChecker to find concurrency bugs.
6-
7-
## Project Structure
8-
9-
P projects follow this structure:
10-
```
11-
ProjectName/
12-
├── ProjectName.pproj # Project configuration (XML)
13-
├── PSrc/ # Source files (state machines, types, events)
14-
│ ├── Enums_Types_Events.p
15-
│ └── MachineName.p
16-
├── PSpec/ # Specification monitors
17-
│ └── SafetyMonitor.p
18-
└── PTst/ # Test files
19-
└── TestDriver.p
20-
```
21-
22-
## Reserved Keywords
23-
24-
DO NOT use these as identifiers:
25-
```
26-
var, type, enum, event, on, do, goto, data, send, announce, receive, case, raise,
27-
machine, state, hot, cold, start, spec, module, test, main, fun, observes, entry,
28-
exit, with, union, foreach, else, while, return, break, continue, ignore, defer,
29-
assert, print, new, sizeof, keys, values, choose, format, if, halt, this, as, to,
30-
in, default, Interface, true, false, int, bool, float, string, seq, map, set, any
31-
```
32-
33-
## Essential Syntax Patterns
34-
35-
### State Machine Declaration
36-
```p
37-
machine MachineName {
38-
var localVar: int;
39-
40-
start state Init {
41-
entry {
42-
// initialization code
43-
}
44-
45-
on eEvent do (payload: PayloadType) {
46-
// handle event
47-
}
48-
49-
on eTrigger goto NextState;
50-
}
51-
52-
state NextState {
53-
entry { ... }
54-
exit { ... }
55-
}
56-
}
57-
```
58-
59-
### Event Declarations
60-
```p
61-
event eSimpleEvent; // No payload
62-
event ePayloadEvent: PayloadType; // With payload
63-
```
64-
65-
### Type Declarations
66-
```p
67-
type TRecord = (field1: int, field2: string);
68-
type TChoice = data | null;
69-
enum EnumName { Value1, Value2, Value3 }
70-
```
71-
72-
### Sending Events
73-
```p
74-
send targetMachine, eEvent, payload; // Send to specific machine
75-
announce eEvent, payload; // Broadcast to monitors
76-
raise eEvent, payload; // Raise to self
77-
```
78-
79-
## Common Mistakes to Avoid
80-
81-
### 1. Variable Initialization
82-
```p
83-
// WRONG - No inline initialization
84-
var x: int = 0;
85-
86-
// CORRECT - Separate declaration and assignment
87-
var x: int;
88-
x = 0;
89-
```
90-
91-
### 2. Event Payload Access
92-
```p
93-
// WRONG - receive() is not valid
94-
var data = receive(eEvent);
95-
96-
// CORRECT - Use handler parameters
97-
on eEvent do (payload: PayloadType) {
98-
// use payload directly
99-
}
100-
```
101-
102-
### 3. Sequence Operations
103-
```p
104-
var mySeq: seq[int];
105-
106-
// WRONG
107-
mySeq += (value);
108-
109-
// CORRECT - Use index-value pair
110-
mySeq += (sizeof(mySeq), value);
111-
```
112-
113-
### 4. Set Operations
114-
```p
115-
var mySet: set[int];
116-
117-
// CORRECT
118-
mySet += (value);
119-
mySet -= (value);
120-
```
121-
122-
### 5. Map Operations
123-
```p
124-
var myMap: map[string, int];
125-
126-
// Add/Update
127-
myMap[key] = value;
128-
129-
// Check existence before access
130-
if (key in myMap) {
131-
var v = myMap[key];
132-
}
133-
```
134-
135-
## Compilation Commands
136-
137-
```bash
138-
# Compile P project
139-
p compile
140-
141-
# Run PChecker
142-
p check -tc TestCaseName
143-
```
144-
145-
## MCP Tools Available
146-
147-
When working with P code, you have two approaches for project generation:
148-
149-
### Recommended: Step-by-Step Generation (use this by default)
150-
151-
Generate files one at a time so the user can review and approve each before proceeding. This produces higher quality results because errors are caught early and the user stays in control.
152-
153-
1. **generate_project_structure** - STEP 1: Create project skeleton (PSrc, PSpec, PTst, .pproj)
154-
2. **generate_types_events** - STEP 2: Generate Enums_Types_Events.p (preview, then save with save_p_file)
155-
3. **generate_machine** - STEP 3: Generate each state machine one at a time (preview, then save with save_p_file). Pass previously saved files as context_files.
156-
4. **generate_spec** - STEP 4: Generate safety specification (preview, then save with save_p_file)
157-
5. **generate_test** - STEP 5: Generate test driver (preview, then save with save_p_file)
158-
6. **p_compile** - STEP 6: Compile the project
159-
7. **fix_compiler_error** / **fix_iteratively** - Fix any compilation errors
160-
8. **p_check** - STEP 7: Run PChecker verification
161-
9. **fix_checker_error** / **fix_buggy_program** - Fix any PChecker errors
162-
163-
### One-Shot: Complete Project Generation (only when explicitly requested)
164-
165-
Use **generate_complete_project** ONLY when the user explicitly asks for fully automated, one-shot, or hands-off generation. This runs all steps without human review.
166-
167-
### Other Useful Tools
168-
169-
- **save_p_file** - Save previewed code to disk after user approves
170-
- **syntax_helper** - Get P language syntax help
171-
- **search_p_examples** - Find similar P code examples
172-
- **list_project_files** / **read_p_file** - Inspect existing P projects
173-
174-
## Human-in-the-Loop Guidance
175-
176-
When fixing errors, the tools will attempt up to 3 automated fixes. If all attempts fail:
177-
178-
1. The tool returns `needs_guidance: true`
179-
2. You will see an `error_summary` and `suggested_questions`
180-
3. Ask the user for guidance based on the questions
181-
4. Call the fix tool again with the `user_guidance` parameter
182-
183-
Example flow:
184-
```
185-
Tool returns: {
186-
"needs_guidance": true,
187-
"error_summary": "Cannot resolve type 'TData'",
188-
"suggested_questions": ["What type should TData be?"]
189-
}
190-
191-
Ask user: "I've tried 3 fixes but the error persists. What type should TData be?"
192-
User: "TData should be (x: int, y: int)"
193-
Call: fix_compiler_error(..., user_guidance="TData should be (x: int, y: int)")
194-
```
195-
196-
## Best Practices
197-
198-
1. **Start with types and events** - Define all shared types in Enums_Types_Events.p first
199-
2. **One machine per file** - Keep each state machine in its own file
200-
3. **Use descriptive names** - Events should start with 'e', e.g., `eRequestData`
201-
4. **Add specs early** - Write specification monitors alongside implementation
202-
5. **Test incrementally** - Compile after each machine, don't wait until the end
203-
6. **Keep states focused** - Each state should have a clear purpose
204-
205-
## Resources
206-
207-
Use these MCP resources for reference:
208-
- `p://guides/syntax` - Complete syntax reference
209-
- `p://guides/machines` - State machine patterns
210-
- `p://guides/types` - Type system guide
211-
- `p://guides/events` - Event handling
212-
- `p://guides/specs` - Specification monitors
213-
- `p://examples/fewshot` - Code examples

Src/PeasyAI/docs/DESIGN_DOCUMENT.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -630,8 +630,8 @@ class GenerateProjectInput(BaseModel):
630630

631631
design_doc: str = Field(
632632
...,
633-
description="The design document describing the P program. Should include "
634-
"<title>, <introduction>, <components>, and <interactions> sections."
633+
description="The design document describing the P program in markdown format. Should include "
634+
"headings: # Title, ## Introduction, ## Components, and ## Interactions."
635635
)
636636

637637
output_dir: str = Field(
@@ -685,11 +685,11 @@ This tool will:
685685
5. Generate test drivers (if enabled)
686686
6. Compile and validate the project
687687
688-
The design document should include:
689-
- <title>: Project name
690-
- <introduction>: System description
691-
- <components>: List of machines/components
692-
- <interactions>: Events and communication patterns
688+
The design document should be in markdown format with these headings:
689+
- `# Title`: Project name
690+
- `## Introduction`: System description
691+
- `## Components`: List of machines/components
692+
- `## Interactions`: Events and communication patterns
693693
"""
694694
)
695695
def generate_project(params: GenerateProjectInput) -> GenerateProjectOutput:

Src/PeasyAI/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ Documentation = "https://p-org.github.io/P/"
5050
# ---------------------------------------------------------------------------
5151
[tool.hatch.build.targets.wheel]
5252
# The installable Python code lives under src/
53-
packages = ["src/core", "src/ui"]
53+
packages = ["src/core", "src/ui", "src/utils"]
5454

5555
[tool.hatch.build.targets.wheel.sources]
5656
# Map src/core → core, src/ui → ui so import paths stay the same
Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
1-
Please generate a design document for the provided P code.
1+
Please generate a design document in markdown format for the provided P code.
22
Here are the instructions on the content that must be included in each section of the design document:
3-
1. Title: the title of the system modeled in the given P code
4-
2. Overview: Brief overview of the system described in the P code
5-
3. Components: For each component or actor in the system, include a brief description of what the component represents and a bulleted list of actions performed by the component
6-
4. Interactions: For each messages/event being passed between different components/machines, include a description of the interaction, a bulleted list of the Source and Target components, payload, all possible effects of this interaction
7-
5. Global Specifications: For each specification, include a bulleted list of correctness properties being checked and assertions made to check these properties
8-
6. Possible Scenarios: Include a bulleted list of all the possible scenarios of component/actor behaviour that could occur within the system
9-
7. Enclose the design document content in <design_document></design_document> tags
3+
1. Title: Use a top-level markdown heading (`# Title`) with the title of the system modeled in the given P code
4+
2. Introduction (`## Introduction`): Brief overview of the system described in the P code, followed by a numbered list of assumptions
5+
3. Components (`## Components`): Split into `### Source Components` and `### Test Components`. For each component or actor, include a brief description of what the component represents, its states, local state (variable names with plain English descriptions, no types), initialization (described in plain English — what information the machine needs to start and how it receives it, without code or type annotations), and a bulleted list of behaviors
6+
4. Interactions (`## Interactions`): For each event being passed between components/machines, include a numbered entry with Source, Target, Payload (described in plain English, no code or type annotations), Description, and Effects
7+
5. Specifications (`## Specifications`): For each specification, include the property name, whether it is a safety or liveness property, and a precise English statement that naturally references the relevant events by name
8+
6. Test Scenarios (`## Test Scenarios`): Include a numbered list of concrete test scenarios with specific machine counts and expected outcomes
Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,26 @@
1-
Write the enums, types, and events required for each of the state machines. If multiple state machines need the same enums, types, and event, write them only once. Do not write duplicate declarations. Return only the generated P code without any explanation attached. Return the P code enclosed in <Enums_Types_Events.p></Enums_Types_Events.p>.
1+
Write the enums, types, and events required for each of the state machines. If multiple state machines need the same enums, types, and events, write them only once. Do not write duplicate declarations.
2+
3+
## CRITICAL SYNTAX RULES:
4+
5+
1. **Type definitions do NOT use trailing commas**, even for single-field named tuples:
6+
```
7+
type tLearnPayload = (learnedValue: int); // CORRECT
8+
type tAcceptorConfig = (learners: seq[machine]); // CORRECT
9+
type tProposePayload = (proposer: machine, num: int); // CORRECT
10+
```
11+
Note: Trailing commas are only used in VALUE expressions like `(field = value,)`, never in type definitions.
12+
13+
2. **Use descriptive, unambiguous field names.** Each type's field names will be used throughout the codebase in payload construction and field access. Choose names that are specific to the type:
14+
- GOOD: `(proposalNumber: int, acceptedValue: int)` — clear what each field means
15+
- BAD: `(value: int)` — ambiguous, easily confused across types
16+
17+
3. **Config types** for machine initialization should match the machine's dependencies exactly. Name them `t<MachineName>Config`:
18+
```
19+
type tProposerConfig = (acceptors: seq[machine], learners: seq[machine], proposerId: int);
20+
type tAcceptorConfig = (learners: seq[machine]);
21+
type tLearnerConfig = (majoritySize: int);
22+
```
23+
24+
4. **Do NOT declare config init events** (like eProposerInit, eAcceptorInit) unless the design explicitly uses the init-event pattern. Most machines use the constructor payload pattern where config is passed via `new Machine(config)`.
25+
26+
Return only the generated P code without any explanation attached. Return the P code enclosed in <Enums_Types_Events.p></Enums_Types_Events.p>.

Src/PeasyAI/resources/instructions/generate_machine.txt

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ IMPLEMENTATION INSTRUCTIONS:
55
For each function:
66
1. Implement the body according to the intended behavior
77
2. Use proper P statements and expressions (conditionals, loops, send statements, etc.)
8-
3. Ensure the implementation aligns with the components in <components></components> tags and interactions in <interactions></interactions> tags
8+
3. Ensure the implementation aligns with the "Components" and "Interactions" sections of the design document
99

1010
## Key Rules:
1111
1. Never use `=` in variable declarations
@@ -29,11 +29,20 @@ Named-tuple types use ``(field = value, ...)`` syntax. Always include ALL fields
2929
CORRECT single-field: send target, eMsg, (reqId = 42,); // trailing comma required!
3030
WRONG single-field: send target, eMsg, (reqId = 42); // missing trailing comma → PARSE ERROR
3131

32+
## CRITICAL — Use Exact Type and Field Names from Enums_Types_Events.p:
33+
When the Enums_Types_Events.p file is provided as context, you MUST use the EXACT type names and field names defined there. Do NOT invent alternative names.
34+
Given: type tPromisePayload = (proposer: machine, highestProposalSeen: int, acceptedValue: int);
35+
CORRECT: fun HandlePromise(msg: tPromisePayload) {{ ... msg.highestProposalSeen ... msg.acceptedValue ... }}
36+
WRONG: fun HandlePromise(msg: tPromise) {{ ... msg.propNum ... msg.value ... }}
37+
The function parameter type annotation MUST match the event's payload type exactly as declared.
38+
3239
## Checklist before returning code:
3340
- Every state has handlers, defer, or ignore for EVERY event this machine could receive.
3441
- The start state entry correctly unpacks the constructor payload (if parameterized).
3542
- All `send` targets are machine-typed variables (never null/default).
3643
- No events, types, or enums are declared in this file.
37-
- Single-field named tuples ALWAYS have a trailing comma: ``(field = value,)``.
44+
- Single-field named tuple VALUES always have a trailing comma: ``(field = value,)``. But type annotations do NOT: ``fun Foo(x: tMyType)`` or ``fun Foo(x: (field: int))``.
45+
- All type names in function signatures match EXACTLY what is declared in Enums_Types_Events.p.
46+
- All field names in payload access (e.g., payload.fieldName) and construction (e.g., (fieldName = value,)) match EXACTLY what is declared in the type definition.
3847

3948
Verify that the generated code strictly follows all the P syntax rules before considering it final. Return only the generated P code without any explanation attached. Return the P code enclosed in XML tags where the tag name is the filename.

Src/PeasyAI/resources/instructions/generate_machine_structure.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ P supports specifying and checking both safety as well as liveness specification
99
You are an expert programming assistant designed to write P programs for the given complex distributed systems. Avoid overreliance on general programming knowledge and strictly adhere to P's specific requirements. Thoroughly review the P language syntax guide before writing code. Refer the provided context files for the correct syntax while writing.
1010

1111

12-
Write the structure of P code for the state machine {machineName}. Include all variable declarations, state declarations, event handlers, and function declarations, BUT leave all function bodies empty (with just placeholder comments). Ensure that the components in <components></components> tags and the interactions in <interactions></interactions> tags are reflected in the structure. The structure should include:
12+
Write the structure of P code for the state machine {machineName}. Include all variable declarations, state declarations, event handlers, and function declarations, BUT leave all function bodies empty (with just placeholder comments). Ensure that the "Components" and "Interactions" sections of the design document are reflected in the structure. The structure should include:
1313

1414
1. Machine declaration with local variable declarations
1515
2. All states with their annotations (start, hot, cold)
@@ -88,6 +88,12 @@ For EVERY state, think about which events could arrive in that state (including
8888

8989
PChecker will report an unhandled-event bug for any event that arrives in a state without a handler, defer, or ignore.
9090

91+
## CRITICAL — Use Exact Type Names from Enums_Types_Events.p:
92+
When the Enums_Types_Events.p file is provided as context, you MUST use the EXACT type names defined there for function parameter annotations and event handler signatures. Do NOT invent alternative names.
93+
Given: event ePromise: tPromisePayload;
94+
CORRECT: on ePromise do HandlePromise; ... fun HandlePromise(msg: tPromisePayload) {{ ... }}
95+
WRONG: on ePromise do HandlePromise; ... fun HandlePromise(msg: tPromise) {{ ... }}
96+
9197
The machine file should NOT contain:
9298

9399
1. Specification monitors (spec keyword)

Src/PeasyAI/resources/instructions/generate_spec_files.txt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
Write P code for the {filename} file. Ensure that the specifications specified in <global_specifications></global_specifications> tags are implemented.
1+
Write P code for the {filename} file. Ensure that the specifications described in the "Specifications" section of the design document are implemented.
22

33
CRITICAL RULES FOR SPEC MONITORS:
44
1. Spec monitors are declared with `spec SpecName observes event1, event2 {{ ... }}`.
55
2. Spec monitors CANNOT use: `this`, `new`, `send`, `announce`, `receive`, `$`, `$$`, `pop`. These are ILLEGAL in monitors and will cause compilation errors.
66
3. Monitors can ONLY observe events and maintain internal state (variables, maps, sets). They assert properties based on observed event payloads.
77
4. If you need to track per-machine state (e.g., per-consumer offsets), use a `map[machine, ...]` keyed by the machine reference from the event payload — NOT `this`.
8-
5. Each safety property from <global_specifications> should be a separate `spec` declaration.
8+
5. Each safety property from the "Specifications" section should be a separate `spec` declaration.
99
6. Every spec monitor MUST contain at least one `assert` statement. A spec that observes events but never asserts anything is useless — PChecker will not detect any bugs. If the property is "X must never happen", assert the negation: `assert !badCondition, "X happened";`.
1010
7. NEVER generate empty functions or functions with only comments. If an event handler doesn't need logic, simply don't observe that event.
1111
8. Only observe events that are actually defined in the Enums_Types_Events.p file. Do NOT observe events that don't exist.
@@ -33,4 +33,10 @@ spec MutualExclusion observes eLockGranted, eLockReleased {{
3333

3434
Note: The spec uses event payloads to track state, asserts on every relevant transition, and never uses `this`/`send`/`new`.
3535

36+
## CRITICAL — Use Exact Type and Field Names from Enums_Types_Events.p:
37+
When the Enums_Types_Events.p file is provided as context, you MUST use the EXACT type names and field names defined there. Do NOT invent alternative names.
38+
Given: event eAccepted: tAcceptedPayload; and type tAcceptedPayload = (proposalNumber: int, acceptedValue: int);
39+
CORRECT: on eAccepted do (payload: tAcceptedPayload) {{ ... payload.acceptedValue ... }}
40+
WRONG: on eAccepted do (payload: tAccepted) {{ ... payload.value ... }}
41+
3642
Verify that the generated code strictly follows all the P syntax rules before considering it final. Return only the generated P code without any explanation attached. Return the P code enclosed in XML tags where the tag name is the filename.

0 commit comments

Comments
 (0)