Recall instead of resend.
One command. No local server, no Python, no database — the history lives on our side and each turn asks for the part that answers the question.
--header "Authorization: Bearer $TILEWARD_API_KEY"
Restart, then /mcp lists the tools. Ask the model to remember something, start a new
thread, and ask it to recall — that round trip is the whole product.
Claude Code sends no per-conversation identifier of its own, so the model passes one, as the
conversation argument on each tool call. Its instructions tell it to and it normally does, but that is a
model-follows-instructions mechanism rather than an enforced one, and a turn that omits it lands in a shared per-key
store. One key per project if cross-project isolation matters to you.
A static header cannot substitute. Headers in an MCP server config are fixed for the life of the connection, so there is no value you could put in one that changes per conversation — the argument is the only control that varies at the right granularity.
A gate, not a system prompt.
Claude Code runs a UserPromptSubmit hook before the model sees a prompt and honours its refusal. A refused prompt costs zero generation tokens, because nothing is generated.
{
"hooks": {
"UserPromptSubmit": [{
"matcher": "",
"hooks": [{
"type": "http",
"url": "https://api.tileward.com/v1/guard/hook",
"headers": { "Authorization": "Bearer $TILEWARD_API_KEY" },
"allowedEnvVars": ["TILEWARD_API_KEY"],
"timeout": 10
}]
}]
}
}
Point at /v1/guard/hook, never at /v1/guard. The plain endpoint answers
with a decision in our own shape; Claude Code blocks only on a body that says {"decision":"block"}, so aiming a
hook at it would allow every prompt while looking installed. /v1/guard/hook exists to speak the hook's contract.
The policy lives server-side on the key. A key with no policy bound to it refuses nothing, so bind one in the console first, under Settings → API keys.
A guardrail you can remove is not a control.
The configuration above is a choice a developer makes and can undo. To govern someone else, it has to come from managed settings, which a user cannot override.
"hooks": { /* the UserPromptSubmit block above */ },
"allowManagedHooksOnly": true,
"strictKnownMarketplaces": true,
"disableSideloadFlags": true
}
Each line closes a specific door and dropping any one turns enforcement back into a suggestion.
allowManagedHooksOnly stops a developer unhooking themselves; strictKnownMarketplaces stops them adding
their own sources; disableSideloadFlags rejects the command-line flags that would bypass both for a single run.
Managed settings live at /Library/Application Support/ClaudeCode/managed-settings.json on macOS,
/etc/claude-code/managed-settings.json on Linux, and the equivalent path on Windows.
What happens when Tileward is unreachable.
An HTTP hook fails open: if Claude Code cannot reach us, the prompt goes to the model and the error is logged. That is Claude Code's behaviour and there is no flag that changes it.
If a prompt must never run unchecked, run the gate as a local script instead of an HTTP hook. A process on the machine can refuse when the network is gone, which an HTTP hook cannot. Even then, be precise about the promise: a local hook still fails open if it is deleted, made non-executable, or exceeds the hook timeout, because those failures happen outside the script. Keep its own timeout well under the hook timeout so the deadline that fires is the one that blocks, and monitor that the file exists.