P4 Core is a minimal local-LLM agent runtime for purpose execution.
- Mainline version:
0.4.1-mainline(refinements after 0.4.0; see canonical handoff) - Mainline date:
2026-04-26(last refined:2026-05-03) - Canonical handoff (entry point): handoff/p4-canonical-mainline-2026-04-26.md
- Coding invariants (must read before changing P4): handoff/p4-coding-invariants-2026-04-24.md
- Latest design notes (refinements after 0.4.0):
- handoff/p4-judge-verdict-first-2026-05-03.md — judge schema verdict-first redesign
- handoff/p4-followthrough-recovery-2026-05-03.md — やり切る invariant + LLM output recovery
- Code root for P4 inheritance:
./ - Verification command:
python3 -m unittest discover -s tests
This is the current P4 baseline. Older planning documents (requirements, design spec, task list, audits) are in handoff/archive/p4/ for history.
It is intentionally smaller than P1/P2:
- session-based chat loop
- tool call -> tool result reinjection
- local Ollama model routing
- raw event persistence
- external dashboard for chat/control/visibility
- passive Japanese live commentary for step-by-step failure analysis
- blocked/success/failed operation status separation
- recursive frame hierarchy for local context isolation
gemma4:26bglm-4.7-flashqwen3-coderdevstral
cd /path/to/P4
python3 -m unittest discover -s tests
python3 -m p4_core.cli --root /tmp/p4-demo version
python3 -m p4_core.cli --root /tmp/p4-demo bootstrap --force
python3 -m p4_core.cli --root /tmp/p4-demo set-goal --text "READMEを読み、実行計画を返す"
python3 -m p4_core.cli --root /tmp/p4-demo chat --message "このworkspaceで最初に確認すべきファイルを読んで"
python3 -m p4_core.cli --root /tmp/p4-demo run-loop
python3 -m p4_core.cli --root /tmp/p4-demo dashboard --host 127.0.0.1 --port 8899P4 records the agent loop as append-only JSONL events. Important event types:
user_messageassistant_messagetool_calltool_resultfinishsystem_noteplanning_noteobserver_noteactivity_updateoperationframe_openedframe_returnedchild_return
Terminal agent turns use a text JSON action contract. If the LLM returns prose, truncated JSON, thinking-only output, or an invalid envelope instead of a valid tool_name / tool_args object, P4 records system_note.code = llm_output_issue. Parse failures are classified as empty_output, thinking_only_output, missing_json_object, json_parse_error, length_truncated, invalid_tool_envelope, json_extraneous_text, schema_validation_failed, or json_contract_not_confirmed. The latest class and stream metadata are also shown in runtime status and the dashboard. In Ollama chat responses, P4 parses assistant content only; thinking is retained as diagnostic evidence but is not treated as tool-call JSON.
Follow-through recovery (やり切る invariant) — see handoff/p4-followthrough-recovery-2026-05-03.md. When the LLM output contains a valid envelope (schema-conformant) but with extraneous text trailing it, P4 does not throw away the whole turn. It records system_note.code = llm_output_recovered with reason_code: json_extraneous_text_recovered, adopts the first valid envelope (deterministically chosen by _extract_json_object as the longest valid object), and proceeds. last_llm_parse_issue ends in _recovered so the recovery is identifiable, and raw_output_is_machine_json: false keeps the strict-purity flag honest. Default json_retry_limit is 2, providing two repair attempts before falling through to recovery.
Judge verdict-first invariant — see handoff/p4-judge-verdict-first-2026-05-03.md. JUDGE_VERDICT_SCHEMA and FINISH_ACCEPTANCE_SCHEMA require only the decision field (verdict / status); annotation fields (reason_code, rationale, etc.) are optional and free-text. This prevents annotation drift (e.g. LLM returning reason_code: "supported_claim" when the prompt example used "supported") from rejecting an otherwise-valid verdict: "ok".
Default Ollama output budgets are intentionally bounded but large enough for JSON tool calls: terminal and coding use num_predict=2048, while reasoning uses 1024 and fast stays at 384. A done_reason of length means the model hit this budget before completing the required JSON envelope.
Action prompts are intentionally compact. They include the current user request, recent tool evidence, important system notes, current frame state, and a short reflection block. They do not replay old observer commentary, unrelated prior tasks, or large failed assistant outputs.
P4 exposes two frame actions to the LLM:
open_child_frame: open a focused child frame withgoalandcontext_summaryreturn_to_parent: return from a child frame withsummaryandfindings
These actions are shown with normal tools in the prompt, but the runtime treats them as kernel control actions. They do not go through ToolExecutor, and they are logged as frame_opened / frame_returned rather than tool_call / tool_result.
Each frame has isolated session_events. Child frame tool calls and tool results are not copied into the parent. The parent receives only a compact child_return event containing the child's return payload. This is the compatibility boundary: tasks that do not use frame actions continue through the P3-style session loop, while decomposed tasks keep local exploration out of the parent context.
Frame working memory starts with four fields: observations, current_focus, unresolved_questions, and avoid_repeating. Tool results update these fields automatically for common actions such as read_file, search_code, and run_command. Frame state is appended under state/frames/frames.jsonl and is also exposed in status_snapshot() and the dashboard snapshot.
Limits:
- maximum depth is 4
- each frame has a 15-step safety valve
finishis blocked inside child frames; child frames must usereturn_to_parent- a new queued user message abandons the previous active frame hierarchy and starts a new root frame
Coding/tool turns run inside a dedicated LLM workspace at workspaces/runs/<turn_id>/. P4 state, sessions, and dashboard logs stay in the root workspace, while read_file, write_file, append_file, replace_text, search_code, and run_command operate inside that turn workspace. The path is recorded as llm_workspace on events and is shown in the dashboard as the current or last work area.
For file edits, avoid putting long source code in one JSON argument. The chunk budget is configured once as runtime.tool_content_chunk_bytes and is used by both the runtime guard and the prompt. If file content exceeds that budget, the LLM should return only the next write_file / append_file chunk in the current step and continue with another append_file step later. Oversized write_file / append_file calls fail with guidance instead of silently accepting brittle payloads.
P4 does not use deterministic controller shortcuts for user work. Command execution and coding tasks go through the normal LLM action loop, with runtime guards validating requested commands, artifacts, and final grounding.
Finish is guarded by required-command checks, expected-artifact checks, and a grounding judge. The grounding judge asks for JSON verdicts and separates ng from judge failures such as invalid_output, invalid_json, empty_output, and error.
Set runtime.observer_enabled in the workspace config to enable the passive live commentator. The commentator is observational only; it does not control, approve, block, or finish tasks.
It runs after:
- an LLM response cannot be parsed as a tool call (
llm_output_issue) - a tool result is recorded
- a finish attempt is blocked
- a native chat response completes
The dashboard shows commentary inside the operation flow. Commentary includes what happened, why the LLM may have produced the failing output, and whether the context passed to the LLM appears noisy or contradictory.
The commentator uses the runtime chat_timeout_seconds; there is no separate shorter commentator timeout.
Controller/fast-path steps do not call the commentator LLM because there is no LLM action output to explain, and commentary must not block the main execution path.
Operation status values are:
runningsuccessfailedblocked
blocked means the task did not pass P4's finish/governance checks, even if the outer runner returned normally.
bootstrapversionstatusollama-statusset-goalchatrun-loopworkerdashboard