Skip to content
Merged
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
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.1.6] — 2026-07-16

### Added

- `agent/` package: typed subagent spawn contracts (`SpawnRequest`,
`SpawnResult`, capability/isolation/subagent-type parse helpers,
`Normalize`/`Validate`) and canonical hook event names with vendor
aliases (`CanonicalHookEvent`). Year 0 Grok behavioral port (PACK-01).
- `proto/hawk/contracts/v1/agent.proto` mirroring `SpawnRequest` /
`SpawnResult` for buf lint/breaking and cross-language codegen.

## [0.1.5] — 2026-07-11

### Added

- `proto/` — a Buf-managed protobuf schema mirroring every exported Go type,
Expand Down
32 changes: 30 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@ go get github.com/GrayCodeAI/hawk-core-contracts
| Package | Key Types | Purpose |
|---------|-----------|---------|
| `types/` | `Severity`, `Finding`, `FindingSlice`, `ParseSeverity` | Severity levels, findings, shared result vocabulary |
| `tools/` | `ToolCall`, `ToolResult` | Provider-neutral tool call and result contracts |
| `tools/` | `ToolCall`, `ToolResult`, `ToolMeta`, `FinalizeResult`, version lifecycle types | Provider-neutral tool call, result, identity, and finalization contracts |
| `events/` | `ToolEvent`, `TraceEvent`, `UsageInfo` | Normalized tool and trace event contracts |
| `policy/` | `Risk`, `PermissionVerdict`, `Allow`, `Deny` | Risk, permission verdict, guardian decision, approval request contracts |
| `review/` | `Result`, `Finding`, `Comment` | Neutral review findings, comments, stats, and result contracts |
| `verify/` | `Report`, `Finding` | Neutral verification findings, stats, and report contracts |
| `sessions/` | `Phase`, `CostAccumulator`, `ParsePhase` | Cross-repo agent session state types |
| `agent/` | `SpawnRequest`, `SpawnResult`, hook event names | Typed subagent spawn + hook vocabulary |

## Architecture

Expand All @@ -36,7 +37,8 @@ hawk-core-contracts (stdlib only)
├── policy/ Risk, PermissionVerdict — governance contracts
├── review/ Result, Finding, Comment — review result contracts
├── verify/ Report, Finding — verification report contracts
└── sessions/ Phase, CostAccumulator — session state contracts
├── sessions/ Phase, CostAccumulator — session state contracts
└── agent/ SpawnRequest, SpawnResult, hook events — subagent spawn contracts
```

## Scope
Expand Down Expand Up @@ -76,6 +78,10 @@ complete:
3. Tool, event, and policy contracts added
4. Review and verification result contracts added
5. Sessions package added for cross-repo session state
6. Tool contract versioning & finalization: `ToolMeta` identity envelope, additive-only
version bump rule, closed `ToolNamespace` enum, `BehaviorPreset` back-compat presets,
and the single-locking `FinalizeToolConfig` lifecycle (with `FinalizeResult`,
`VersionWarning`, `FinalizeConfigViolation`)

## Ecosystem

Expand All @@ -96,6 +102,28 @@ hawk-core-contracts is a **foundation repo** in the hawk-eco mono-ecosystem:
`hawk` and all engines import `hawk-core-contracts` when they share a real
cross-repo contract; the repo itself never imports back.

## Tool Contract Versioning & Finalization

Tool configuration has a single locking **finalize** step instead of a scattered set of
enable/disable/options calls that can leave the tool set in an inconsistent state.

**`ToolMeta`** is the canonical identity envelope attached to every tool-call event. Its
`version` string is **additive-only**: new additive fields do not change it; only breaking
changes (field removal, type change, reserved-range change) bump it. `namespace` is a
**closed enum** (`ToolNamespace`) — a new unknown namespace intentionally fails strict typed
deserialization (`ToolNamespaceFrom` returns an error), so a forward-rolled contract can't
silently mis-route tools to a harness that doesn't understand them. `label` is a
cross-harness grouping key; `read_only` marks tools the harness must treat as side-effect free.

**`FinalizeToolConfig`** is the single, locking RPC that atomically commits a
`behavior_version` (a `BehaviorPreset`: `current`, `legacy`, or unspecified) plus an
enabled-tool set, and returns any `VersionWarning`s (non-fatal drift) or
`FinalizeConfigViolation`s (fatal, deterministic failures). After a call where
`FinalizeResult.Ok()` returns true (`Finalized && no violations`), tools are safe to call.

This models the lifecycle in SpaceXAI `grok`, but starts clean — the old scattered
Enable/Disable/Get/Set-ToolOptions surface is intentionally absent.

## Ecosystem Boundaries

Rules that keep this repo at the foundation layer:
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.1.5
0.1.6
68 changes: 68 additions & 0 deletions agent/hooks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package agent

import "strings"

// Hook event names for lifecycle and tool gates.
//
// Hawk may accept vendor aliases (Claude/Cursor); normalize to these
// canonical names at the boundary. Constants are shared so hawk, plugins,
// and SDKs agree on wire/event vocabulary without importing engines.
const (
HookPreToolUse = "PreToolUse"
HookPostToolUse = "PostToolUse"
HookUserPromptSubmit = "UserPromptSubmit"
HookSessionStart = "SessionStart"
HookSessionEnd = "SessionEnd"
HookStop = "Stop"
HookSubagentStart = "SubagentStart"
HookSubagentStop = "SubagentStop"
HookNotification = "Notification"
HookPermissionRequest = "PermissionRequest"
HookPreCompact = "PreCompact"
HookFailure = "Failure"
)

// VendorHookAliases maps common third-party hook names to canonical Hawk names.
// Keys are lower-case for case-insensitive lookup.
var VendorHookAliases = map[string]string{
"pretooluse": HookPreToolUse,
"pre_tool_use": HookPreToolUse,
"posttooluse": HookPostToolUse,
"post_tool_use": HookPostToolUse,
"userpromptsubmit": HookUserPromptSubmit,
"user_prompt_submit": HookUserPromptSubmit,
"sessionstart": HookSessionStart,
"session_start": HookSessionStart,
"sessionend": HookSessionEnd,
"session_end": HookSessionEnd,
"stop": HookStop,
"subagentstart": HookSubagentStart,
"subagent_start": HookSubagentStart,
"subagentstop": HookSubagentStop,
"subagent_stop": HookSubagentStop,
"notification": HookNotification,
"permissionrequest": HookPermissionRequest,
"permission_request": HookPermissionRequest,
"precompact": HookPreCompact,
"pre_compact": HookPreCompact,
"failure": HookFailure,
"onerror": HookFailure,
"on_error": HookFailure,
}

// CanonicalHookEvent returns the Hawk canonical event name for s, or "" if unknown.
func CanonicalHookEvent(s string) string {
if s == "" {
return ""
}
switch s {
case HookPreToolUse, HookPostToolUse, HookUserPromptSubmit, HookSessionStart,
HookSessionEnd, HookStop, HookSubagentStart, HookSubagentStop,
HookNotification, HookPermissionRequest, HookPreCompact, HookFailure:
return s
}
if c, ok := VendorHookAliases[strings.ToLower(strings.TrimSpace(s))]; ok {
return c
}
return ""
}
22 changes: 22 additions & 0 deletions agent/hooks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package agent

import "testing"

func TestCanonicalHookEvent(t *testing.T) {
cases := []struct {
in, want string
}{
{HookPreToolUse, HookPreToolUse},
{"pre_tool_use", HookPreToolUse},
{"PreToolUse", HookPreToolUse},
{"subagent_start", HookSubagentStart},
{"on_error", HookFailure},
{"", ""},
{"not-a-hook", ""},
}
for _, tc := range cases {
if got := CanonicalHookEvent(tc.in); got != tc.want {
t.Errorf("CanonicalHookEvent(%q)=%q want %q", tc.in, got, tc.want)
}
}
}
Loading
Loading