Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
spike_layer=1-source-type-contract
image=tg-worker:dev
image_id=sha256:ac1cb183e5b2c82982f6473fef86ccf128763a31192d711526d843a79edb69ff
image_created=2026-08-21T13:00:21.057325912+08:00
network=none
openclaw_version=OpenClaw 2026.4.14 (2f35b6f)
{
"source": {
"hookTypes": {
"path": "src/plugins/hook-types.ts",
"bytes": 18405,
"lines": 694,
"sha256": "f4ae27785097debeed4e7aba3ee8b88d94e2e97e9d67e4ba39e55deccaf1948a"
},
"hooks": {
"path": "src/plugins/hooks.ts",
"bytes": 36561,
"lines": 1160,
"sha256": "24abd7601d0ef490d43013630d5a8da6b8cd9a4e9e1e2deed1ac6a9b64f646cf"
},
"hookRunnerGlobal": {
"path": "src/plugins/hook-runner-global.ts",
"bytes": 3022,
"lines": 106,
"sha256": "dff10b06b7b726ae353f787f15d3983a2effe3c6e9125d5b1bd0cbf6a87f30b9"
},
"beforeToolCall": {
"path": "src/agents/pi-tools.before-tool-call.ts",
"bytes": 15826,
"lines": 464,
"sha256": "e96a13e0e23b8115087098f23d0e6222527cca98f3fa2e8f1a59b2237c2dc4b8"
},
"promptHelpers": {
"path": "src/agents/pi-embedded-runner/run/attempt.prompt-helpers.ts",
"bytes": 8655,
"lines": 258,
"sha256": "9c9d4c161b5d0338b8fb87ece846f125e904bc967e2ffa29d687d4a14a097758"
},
"attempt": {
"path": "src/agents/pi-embedded-runner/run/attempt.ts",
"bytes": 99293,
"lines": 2466,
"sha256": "1fbc0d09651a7378a0177667a3511c31ead8e87b29db14c8d9dd5cd1f95ba627"
},
"persistGuard": {
"path": "src/agents/session-tool-result-guard.ts",
"bytes": 9880,
"lines": 271,
"sha256": "40a26a7b41ad68dc209727dda9da7e2c92d45df5fbe2f93d19f3f15462b21f43"
},
"bootstrapFiles": {
"path": "src/agents/bootstrap-files.ts",
"bytes": 7651,
"lines": 243,
"sha256": "2f5210d60134de8a3235beb24f7993f11e9acec58ea799abb2e3017d6c5f901d"
},
"bootstrapHelpers": {
"path": "src/agents/pi-embedded-helpers/bootstrap.ts",
"bytes": 8985,
"lines": 289,
"sha256": "46f9223f67612ce49d0d60fb2e2e84d079a7b38ecde314829935d76d0d568bf7"
},
"workspace": {
"path": "src/agents/workspace.ts",
"bytes": 20054,
"lines": 648,
"sha256": "c675c2fc6015ed49fd919add48cefd86c57b8dd4a82c3fb25250ffcd60bff590"
}
},
"before_tool_call": {
"typed_async": {
"line": 633,
"text": ") => Promise<PluginHookBeforeToolCallResult | void> | PluginHookBeforeToolCallResult | void;"
},
"runner_sequential": {
"line": 782,
"text": "return runModifyingHook<\"before_tool_call\", PluginHookBeforeToolCallResult>("
},
"caller_awaits_runner": {
"line": 203,
"text": "const hookResult = await hookRunner.runBeforeToolCall("
},
"failure_policy": {
"line": 44,
"text": "before_tool_call: \"fail-closed\","
},
"hook_runner_timeout_enforcement": false
},
"tool_result_persist": {
"typed_sync_only": {
"line": 638,
"text": "tool_result_persist: ("
},
"runner_sync_only": {
"line": 827,
"text": "* This hook is intentionally synchronous: it runs in hot paths where session"
},
"promise_guard": {
"line": 852,
"text": "`[hooks] tool_result_persist handler from ${hook.pluginId} returned a Promise; ` +"
},
"promise_error_policy": {
"line": 854,
"text": "if (shouldCatchHookErrors(\"tool_result_persist\")) {"
},
"persistence_calls_transform_before_append": {
"line": 198,
"text": "const persisted = applyBeforeWriteHook("
},
"append_after_transform": {
"line": 208,
"text": "return originalAppend(persisted as never);"
},
"default_failure_policy_expression": {
"line": 215,
"text": "catchErrors && (failurePolicyByHook[hookName] ?? \"fail-open\") === \"fail-open\";"
},
"configured_global_policy_only": {
"line": 43,
"text": "failurePolicyByHook: {"
}
},
"before_prompt_build": {
"typed_async": {
"line": 576,
"text": "before_prompt_build: ("
},
"runner_sequential": {
"line": 534,
"text": "return runModifyingHook<\"before_prompt_build\", PluginHookBeforePromptBuildResult>("
},
"caller_catches_and_continues": {
"line": 52,
"text": "log.warn(`before_prompt_build hook failed: ${String(hookErr)}`);"
},
"caller_returns_undefined_after_failure": {
"line": 53,
"text": "return undefined;"
},
"missing_hook_returns_no_result": {
"line": 43,
"text": "? await params.hookRunner"
},
"final_model_call_uses_effective_prompt": {
"line": 2011,
"text": "activeSession.prompt(effectivePrompt, { images: imageResult.images }),"
}
},
"bootstrap": {
"load_and_hook_override": {
"line": 208,
"text": "const updated = await applyBootstrapHookOverrides({"
},
"context_build": {
"line": 236,
"text": "const contextFiles = buildBootstrapContextFiles(bootstrapFiles, {"
},
"missing_file_becomes_prompt_text": {
"line": 225,
"text": "const missingText = `[MISSING] Expected at: ${pathValue}`;"
},
"truncation_is_allowed": {
"line": 251,
"text": "`workspace bootstrap file ${file.name} is ${trimmed.originalLength} chars (limit ${trimmed.maxChars}); truncating in injected context`,"
},
"digest_or_checksum_in_bootstrap_sources": false
}
}

current_tiangong_plugin_contract_lines
worker/agent/skills/runtime.mjs:34: return { prependContext: promptContext(runtime, ctx) };
worker/agent/skills/runtime.mjs:60:export function registerAgentPackageRuntime(api, options = {}) {
worker/agent/gates/tool-result-capture.mjs:111: store.appendSync(record);
worker/plugin/index.mjs:17:import { registerAgentPackageRuntime } from "../agent/skills/runtime.mjs";
worker/plugin/index.mjs:56: registerAgentPackageRuntime(api, { env: process.env });
worker/plugin/index.mjs:57: api.on("tool_result_persist", createToolResultCaptureHook({
worker/plugin/index.mjs:60: }), { priority: 100 });
70 changes: 70 additions & 0 deletions smoke-testing/runs/2026-08-22-m9-a0-openclaw-2026-4-14/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# M9-A0 pinned OpenClaw spike

## Scope

- Issue: #110
- Design: `docs/design/m9-professional-agent-runtime-and-project-knowledge.md`, §§16.1, 17.1, 20.1
- Component under test: OpenClaw `2026.4.14` (`2f35b6f`) and the Tiangong control plugin path
- Run type: focused research/integration spike; the final layer is one Basic Matrix member turn
- Product boundary: this run does not implement or enable M9-A and does not authorize an OpenClaw upgrade

## Contract and owners

- OpenClaw owns the pinned hook registry, runner, prompt/session path, and official Matrix Channel Plane.
- Tiangong owns the control plugin, admission decisions, bootstrap contract decision, ToolResult capture decision, and sanitized report.
- Fake provider and fake coordination endpoint are test-only fixtures owned by this run.
- The disposable workspace and any final reserved Matrix fixture must be created by the run, recorded by stable identifier, and cleaned only by the run.

## Provider and target identity

- Layers 1–3: no external model provider; use a deterministic fake provider and capture the final request without credentials.
- Layer 4: use the official Channel Plane with one reserved safe member fixture. Record the exact owned fixture identity and current turn correlation in `result.md` before execution.
- No production Work, Task, room, repository, credential, or unrestricted transcript is in scope.

## Prerequisites

- Repository branch is based on merged `develop` and keeps the pinned OpenClaw version at `2026.4.14`.
- Actual candidate Worker image reports the pinned OpenClaw version and source/type paths before any hook runner test.
- Docker, Node, the repository deterministic checks, and the local AgentTeams/Matrix stack are available only for the layer that requires them.
- No layer may silently change the OpenClaw version, provider, model, prompt, fixture, timeout, or isolation policy.

## Serial gates

A later layer is blocked unless the previous layer has a passing direct machine result:

1. **Source/type/contract inspection** — inspect hook types, registration, ordering, await/block, throw/timeout, priority, error policy, bootstrap loading, and final prompt assembly.
2. **Container hook runner + fake provider** — run the real runner in the actual image with no external network; verify blocking, final request, persistence ordering, replay, truncation, bootstrap placement, and corrupt/missing bootstrap behavior.
3. **Deterministic integration** — run the real plugin and gateway/session path with fake provider, fake coordination, synthetic Task and Work-Leader admission, runtime binding/claim fixtures, and disposable workspace.
4. **Basic Matrix turn** — use the official Channel Plane once to confirm the already-proven path; it cannot prove authorization, concurrency, atomicity, or recovery.

If any layer fails, stop immediately, preserve sanitized evidence, and do not run later layers or begin M9-A implementation.

## Required observations

- `before_tool_call`: synchronous gate from the caller's perspective; await behavior, block result, thrown error, timeout, priority, and whether the tool executes.
- `tool_result_persist`: synchronous-only contract, exact ordering relative to transcript persistence and return to the model, replay behavior, truncation, and failure behavior.
- `before_prompt_build`: final request/system context, mutation fields, ordering, priority, and failure behavior.
- Immutable bootstrap: selected `AGENTS.md`/`SOUL.md` content reaches the final provider request; missing, corrupt, or digest-mismatched input prevents the model call rather than degrading to ordinary prompt context.
- Observation-only hooks remain observations and are not treated as fail-closed controls.

## Evidence and sanitization

- Store direct, bounded machine evidence under this run directory with pinned versions, source paths/line ranges, stable fixture IDs, event ordering, exit codes, and sanitized error codes.
- Do not store credentials, tokens, provider configuration, private session material, raw Matrix transcripts, unrestricted prompts, or unrestricted logs.
- Keep model/provider response claims separate from hook outcomes, persistent records, and external Matrix observations.

## Timeout and cleanup

- Each layer has an explicit command timeout recorded in `result.md`; no fixed sleep is used as readiness proof.
- Cleanup runs after every started layer and removes only this run's disposable workspace, fake services, containers, and reserved fixture resources.
- Cleanup failure keeps the run red and is reported with the exact owned resource residue.

## Deliverables

- `plan.md` — this pre-run scope and gate plan.
- `result.md` — per-layer pass/fail/blocked status, direct facts, limitations, and integration decision.
- Bounded evidence files referenced by `result.md`.

## Decision rule

The spike is research evidence only. A passing result selects the actual hook/bootstrap integration points for a later M9-A implementation PR. A failed or inconclusive premise requires a design revision and review before any implementation; no prompt, Skill, Gate, or manual process may be used as a substitute for a missing runtime safety boundary.
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# M9-A0 pinned OpenClaw spike result

## Overall decision

**STOPPED at layer 1.** The pinned source/type/contract inspection completed, but it found blocking contract mismatches. Layers 2–4 were not started, no M9-A runtime implementation was begun, and no OpenClaw upgrade was attempted.

This result is research evidence, not evidence that M9-A is implemented.

## Layer status

| Layer | Status | Reason |
|---|---|---|
| 1. Source/type/contract inspection | **FAIL / stop** | The pinned runtime does not provide the required fail-closed immutable bootstrap and ToolResult capture contract at the inspected boundaries. |
| 2. Container hook runner + fake provider | **BLOCKED** | Required serial gate: layer 1 failed. |
| 3. Deterministic plugin/gateway/session integration | **BLOCKED** | Required serial gate: layer 2 was not started. |
| 4. Basic Matrix turn | **BLOCKED** | Required serial gate: layer 3 was not started; no Matrix fixture was used. |

## Direct machine facts

- Candidate image: `tg-worker:dev`
- Candidate image ID: `sha256:ac1cb183e5b2c82982f6473fef86ccf128763a31192d711526d843a79edb69ff`
- Pinned runtime observation: `OpenClaw 2026.4.14 (2f35b6f)`
- Inspection ran with Docker network disabled.
- Source file sizes and SHA-256 values, hook line references, and current Tiangong registration lines are in [`evidence/source-inspection.txt`](evidence/source-inspection.txt).

## Blocking findings

### 1. `before_prompt_build` is not fail-closed

The typed hook is asynchronous and the hook runner executes it sequentially, but the embedded attempt prompt helper catches a hook error, logs a warning, returns `undefined`, and continues to the model path. A missing hook also produces no result. The final call still reaches `activeSession.prompt(effectivePrompt, ...)`.

This cannot enforce the M9 requirement that a missing, corrupt, or invalid immutable bootstrap prevents any model call.

### 2. Bootstrap input is ordinary mutable context, not an immutable verified boundary

The inspected bootstrap path loads workspace files, applies internal hook overrides, and builds context files. Missing files are represented as `[MISSING] Expected at: ...`; oversized files may be truncated. No digest/checksum validation was found in the inspected bootstrap sources.

The current Tiangong package runtime returns `prependContext`, and the current ToolResult capture writes a local `store.appendSync(record)`. Neither observation proves immutable bootstrap entry into the final system input or control-domain spool closure.

### 3. `tool_result_persist` cannot provide the required awaited control-spool boundary as currently defined

The typed hook and runner are synchronous-only. Promise returns are detected and ignored or handled according to the hook failure policy. The global runner configures `before_tool_call` as fail-closed but does not configure `tool_result_persist`; the default policy expression is fail-open. The transcript guard applies the transform before the local transcript append, but the hook cannot await a trusted control-domain ingest.

Therefore the current source contract does not prove “closed in control domain before result returns to the model”; the current Tiangong implementation only appends a local capture record synchronously.

### 4. No hook-level timeout contract was found for `before_tool_call`

The inspected `runBeforeToolCall` runner block has no timeout/abort enforcement. Approval timeout is a separate path and is not a timeout contract for the admission hook itself. This remains unresolved until a later design revision or a pinned integration path proves the required behavior.

## Cleanup and resource ownership

- No fake provider, fake coordination endpoint, disposable workspace, Worker, Matrix room, or external resource was created.
- Docker inspection containers exited and the temporary copied source directory was removed after evidence generation.
- No credential, token, provider configuration, or raw session transcript was written to the repository.

## Required follow-up

1. Open a design revision issue/PR for the failed pinned contracts, preserving this result and direct evidence.
2. Decide whether the pinned OpenClaw source can support a Tiangong-owned fail-closed wrapper/control boundary without relying on fail-open prompt or synchronous observation hooks.
3. Re-review the revised contract before rerunning layer 1 or starting layers 2–4.
4. Do not begin M9-A implementation until a new spike passes all four layers in order.
Loading