Tileward / Docs / Claude Code
Docs · Claude Code

Tileward in Claude Code.

Context as an MCP server, Governance as a hook that runs before the model. Both read the same API key, and neither needs anything running on your machine.

Context

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.

claude mcp add --transport http tileward-context https://context.tileward.com \
  --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.

Governance

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.

// .claude/settings.json — no local script to deploy
{
  "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.

For an organisation

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.

Fail closed, and the limit of it

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.