hustle

package
v0.26.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 14, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

README

pkg/hustle

pkg/hustle defines parallel background work the session can run alongside its loops. A hustle is a named, secret-free, replayable inference invocation that runs in one of two participation lanes — blocking (the loop waits for it) or background (it publishes results when done). The scheduler runtime lives in internal/hustleruntime; this package owns the immutable definitions, the typed audit taxonomy, and the secret-free descriptor projection.

What is hustle?

  • Definition — an immutable hustle recipe built with hustle.Define(opts...): a stable Name, a participation lane, a model source (the originating loop's binding or a named model), a system prompt, optional structured output schema, timeout, and payload limits.
  • BoundDefinition — a hustle bound to a session: the runtime view a Controller schedules against. Session binding retains the model resolver but does not build optional evidence tools.
  • EvidenceBindings — the invocation-scoped origin and read-only workspace root used by BoundDefinition.BindEvidenceTools. The evidence catalog is rebuilt for the exact requesting session/loop; this binding cannot carry mutation, delegation, gate, grant, observation, session, or loop-control capabilities.
  • DefinitionDescriptor — the complete secret-free behavioral projection used by rig identity and durable audit records (the Name, Participation, ModelSource, prompt SHA-256, output-schema SHA-256, policy revisions, timeout, limits). It is what an audit record carries; the raw prompt and inference client never leave the runtime.
  • RunID / Stage / ReasonCode / TerminalStatus — the typed audit taxonomy. Stage names the bounded execution phase in which a hustle failed (Queue → ModelResolution → Inference → Output → Terminal → Finalization). ReasonCode is the bounded, security-safe classification. ReasonAllowed(stage, reason) closes the matrix so impossible stage/reason audit records cannot be written.
  • ParticipationBlocking or Background, the session-global execution lane.
  • ModelSourceCurrentLoop (use the originating loop's live binding) or Named (use a named model). The two are mutually exclusive in a descriptor.

How to use

Hustles are composed into a rig, not invoked directly:

summarizer, err := hustle.Define(
    hustle.WithName("summarize-transcript"),
    hustle.WithParticipation(hustle.ParticipationBackground),
    hustle.WithModelSource(hustle.ModelSourceCurrentLoop),
    hustle.WithSystem("You summarize agent transcripts."),
    hustle.WithTimeout(30*time.Second),
    hustle.WithLimits(hustle.Limits{InputBytes: 1<<20, OutputBytes: 1<<20}),
    hustle.WithPromptRevision("2026-07-21.1"),
    hustle.WithPolicyRevision("2026-07-21.1"),
    // optional structured output:
    // hustle.WithOutputSchema(name, schema, strict, revision),
)
if err != nil { return err }

r, err := rig.Define(
    rig.WithLoops(operator),
    rig.WithHustles(summarizer),
    rig.WithHustleLimits(rig.HustleLimits{
        BlockingConcurrent: 1, BlockingQueued: 8,
        BackgroundConcurrent: 2, BackgroundQueued: 16,
        AuditTimeout:        5*time.Second,
        FinalizationTimeout: 30*time.Second,
        WorkerDrainTimeout:  5*time.Second,
    }),
    /* ... */
)

A loop invokes a hustle through the hustle tool (built by the composition root) and observes the outcome as a typed event on the session stream (HustleStarted, HustleCompleted, HustleFailed).

Tool-using hustles and model capability requirements

A hustle whose Definition carries both an EvidenceToolPolicy (bound evidence tools) and an OutputSchema — the shape a permission-review classifier uses — runs a bounded, sequential tool-use loop: the model may issue zero or more ordinary evidence-tool calls before returning exactly one strict terminal structured result. This requires the resolved model.Model to report Caps.Tools, Caps.StructuredOutput, AND Caps.StructuredOutputWithTools all true; a mismatch fails the hustle before inference (inference.StructuredOutputWithToolsUnsupportedError) rather than degrading to a text response. When such a hustle backs a permission-review classifier, that failure is one of the many expected review outcomes that leaves the ordinary human gate open — see pkg/gate/README.md#permission-review for the full classifier composition and human-fallback story; this package owns only the bounded tool-use loop the classifier runs inside, never the review domain itself.

Sibling packages

  • pkg/rigrig.WithHustles and rig.WithHustleLimits register hustles and bound their lanes; rig.WithPermissionClassifiers automatically registers a classifier's own hustle.Definition as a blocking Hustle.
  • pkg/gate — the permission-review domain (gate.PermissionClassifier, gate.PermissionReviewSubject) a classifier's tool-using Hustle serves; owns evidence-boundary (EvidenceAccessEvaluator/EvidenceContainmentVerifier), audit, and restore semantics for that use case.
  • pkg/eventevent.HustleStarted / HustleCompleted / HustleFailed, the durable lifecycle events.
  • pkg/identityidentity.AgentName used by the originating loop's binding.
  • github.com/looprig/inferenceinference.Client, model.Model, inference.OutputSchema.
  • github.com/looprig/classifiers — the classifier product built on this package's bounded tool-use loop and pkg/gate's review domain. This package never imports it.

How it is designed

       hustle.Definition (immutable recipe)
                │
                │  rig.Define + Bind
                ▼
       hustle.BoundDefinition (runtime view)
                │
                │  invocation origin + read-only evidence binding
                ▼
   ┌────────────────────────────────────────────────────┐
   │ internal/hustleruntime.Controller                   │
   │  two lanes (Blocking | Background)                  │
   │  per-lane: Concurrent in-flight + Queued waiting    │
   │  RunIDFactory mints candidate ids before commit    │
   │  AuditPublisher  ──► durable HustleStarted/...     │
   │  FaultReporter   ──► typed controller faults        │
   │  ActivityTracker ──► session-active-set accounting │
   └────────────────────────────────────────────────────┘
                │
                ▼
        inference.Client  →  HustleCompleted / HustleFailed (durable, stage+reason)
Two lanes, bounded by limits

rig.HustleLimits bounds both lanes independently: each has a Concurrent in-flight cap and a Queued waiting-capacity cap; their sum is the total ownership cap (MaxHustleQueued is the largest configured waiting capacity either lane may take). Blocking runs hold the loop's turn; background runs publish results when complete. A background hustle that outlives its session drains on shutdown within WorkerDrainTimeout.

Secret-free audit by construction

The DefinitionDescriptor is the only thing an audit record carries. Raw prompts and inference clients never leave the runtime; the prompt is hashed (PromptSHA256), the output schema is hashed (OutputSchemaSHA256), and the model is captured as a model.ModelKey plus a policy revision. An audit record therefore cannot leak a prompt or a model's secret configuration, and a re-deployment that changes either is detectable by comparing descriptors.

Closed stage/reason matrix

Stage and ReasonCode are closed enums, and ReasonAllowed(stage, reason) closes the matrix: an impossible combination (e.g. a Finalization stage with a ReasonInference reason) is rejected before it ever reaches an audit record. The closed matrix is the invariant that makes hustle usage aggregates trustworthy.

Documentation

Index

Constants

View Source
const (
	// MaxEvidenceToolDefinitions bounds the number of definitions in one
	// evidence policy before any catalog-sized allocation or digest work.
	MaxEvidenceToolDefinitions = 64
	// MaxEvidenceProducedToolNames bounds all concrete tool names declared by
	// one evidence policy, including names spread across bundle definitions.
	MaxEvidenceProducedToolNames = 128
	// MaxEvidenceToolNameBytes bounds definition and concrete tool names by
	// encoded UTF-8 bytes, not runes.
	MaxEvidenceToolNameBytes = 64
	// MaxEvidenceToolPolicyRevisionBytes bounds the canonical policy revision
	// by encoded UTF-8 bytes.
	MaxEvidenceToolPolicyRevisionBytes = 128
)
View Source
const (
	// MaxEvidenceToolDescriptionBytes bounds each concrete tool description
	// by encoded UTF-8 bytes before the metadata is retained or fingerprinted.
	MaxEvidenceToolDescriptionBytes = 4 << 10
	// MaxEvidenceToolSchemaBytes bounds each concrete tool's raw JSON schema
	// before validation and whitespace compaction.
	MaxEvidenceToolSchemaBytes = 1 << 20
	// MaxEvidenceToolMetadataBytes bounds the aggregate model-facing concrete
	// tool names, descriptions, and compact schemas in one bound catalog.
	MaxEvidenceToolMetadataBytes = 4 << 20
)

Variables

This section is empty.

Functions

func IsRecoverableTerminalValidationError

func IsRecoverableTerminalValidationError(err error) bool

IsRecoverableTerminalValidationError reports whether err contains the package-owned malformed-terminal marker. Matching is typed and never inspects error text.

func NewRecoverableTerminalValidationError

func NewRecoverableTerminalValidationError() error

NewRecoverableTerminalValidationError returns the sealed marker a strict classifier adapter may return when terminal decoding or wire-shape validation is malformed but safe to retry. It must not be used for domain decisions, basis mismatches, unsafe results, or operational failures.

func ReasonAllowed

func ReasonAllowed(stage Stage, reason ReasonCode) bool

ReasonAllowed reports whether reason is a valid durable classification for stage. The closed matrix prevents impossible stage/reason audit records.

Types

type BindError

type BindError struct {
	Kind  BindErrorKind
	Cause error
}

BindError reports why an immutable definition could not be bound.

func (*BindError) Error

func (e *BindError) Error() string

func (*BindError) Unwrap

func (e *BindError) Unwrap() error

type BindErrorKind

type BindErrorKind string

BindErrorKind identifies a definition binding failure.

const (
	BindInvalidDefinition    BindErrorKind = "invalid_definition"
	BindInvalidContext       BindErrorKind = "invalid_context"
	BindMissingModelResolver BindErrorKind = "missing_model_resolver"
	BindInvalidEvidenceTools BindErrorKind = "invalid_evidence_tools"
)

type Bindings

type Bindings struct {
	Models ModelResolver
}

Bindings supplies runtime collaborators needed by a definition.

type BoundDefinition

type BoundDefinition interface {
	Name() Name
	Participation() Participation
	Timeout() time.Duration
	Limits() Limits
	Descriptor() DefinitionDescriptor
	ResolveInference(context.Context, uuid.UUID) (InferenceBinding, error)
	SystemPrompt() string
	OutputSchema() (*inference.OutputSchema, bool)
	EvidenceToolPolicy() (EvidenceToolPolicy, bool)
	RetryPolicy() RetryPolicy
	BindEvidenceTools(context.Context, EvidenceBindings) ([]BoundEvidenceTool, error)
	// contains filtered or unexported methods
}

BoundDefinition is the sealed runtime view of one immutable definition.

type BoundEvidenceTool

type BoundEvidenceTool struct {
	// contains filtered or unexported fields
}

BoundEvidenceTool is an immutable, fingerprinted evidence capability. Its model-facing metadata is frozen separately from the concrete execution tool so optional capabilities on that tool remain available to the runtime.

func (BoundEvidenceTool) DescriptionSHA256

func (b BoundEvidenceTool) DescriptionSHA256() [sha256.Size]byte

func (BoundEvidenceTool) IdentitySHA256

func (b BoundEvidenceTool) IdentitySHA256() [sha256.Size]byte

func (BoundEvidenceTool) Info

func (b BoundEvidenceTool) Info() *tool.ToolInfo

Info returns a defensive copy of the exact metadata frozen at bind time. Execution uses Tool so optional capabilities on the concrete tool are preserved; runtimes must use this accessor for model-facing metadata.

func (BoundEvidenceTool) Name

func (b BoundEvidenceTool) Name() string

func (BoundEvidenceTool) SchemaSHA256

func (b BoundEvidenceTool) SchemaSHA256() [sha256.Size]byte

func (BoundEvidenceTool) Tool

type Definition

type Definition struct {
	// contains filtered or unexported fields
}

Definition is an immutable hustle definition. Its zero value is invalid.

func Define

func Define(opts ...Option) (Definition, error)

Define validates and freezes one text-only hustle definition.

func (Definition) Bind

func (d Definition) Bind(ctx context.Context, bindings Bindings) (BoundDefinition, error)

Bind validates runtime collaborators and returns a read-only bound view.

func (Definition) Descriptor

func (d Definition) Descriptor() DefinitionDescriptor

Descriptor returns the definition's secret-free behavioral projection.

func (Definition) EvidenceToolPolicy

func (d Definition) EvidenceToolPolicy() (EvidenceToolPolicy, bool)

EvidenceToolPolicy returns an independently owned policy slice when evidence tools are enabled.

func (Definition) Limits

func (d Definition) Limits() Limits

Limits returns the definition's immutable payload limits.

func (Definition) Name

func (d Definition) Name() Name

Name returns the stable registration name.

func (Definition) Participation

func (d Definition) Participation() Participation

Participation returns the definition's fixed execution lane.

func (Definition) PolicyRevision

func (d Definition) PolicyRevision() string

PolicyRevision returns the stable digest of all behavior-affecting fields.

func (Definition) RetryPolicy

func (d Definition) RetryPolicy() RetryPolicy

RetryPolicy returns the immutable bounded retry behavior.

func (Definition) Timeout

func (d Definition) Timeout() time.Duration

Timeout returns the definition's exact invocation timeout.

type DefinitionDescriptor

type DefinitionDescriptor struct {
	Name                     Name
	Participation            Participation
	ModelSource              ModelSource
	NamedModelKey            model.ModelKey
	NamedModelPolicyRevision string
	PromptRevision           string
	PromptSHA256             [sha256.Size]byte
	OutputSchemaName         string `json:",omitzero"`
	// OutputSchemaSHA256 covers Description, compact Schema JSON, and Strict.
	// It is a behavioral digest; no raw output policy crosses this boundary.
	OutputSchemaSHA256              [sha256.Size]byte `json:",omitzero"`
	StructuredOutputRevision        string            `json:",omitzero"`
	PolicyRevision                  string
	TimeoutNanos                    int64
	Limits                          Limits
	EvidenceToolPolicyRevision      string            `json:",omitzero"`
	EvidenceToolDefinitionsSHA256   [sha256.Size]byte `json:",omitzero"`
	EvidenceProducedToolNamesSHA256 [sha256.Size]byte `json:",omitzero"`
	EvidenceToolLimits              ToolLoopLimits    `json:",omitzero"`
	EvidenceToolDefinitionCount     int               `json:",omitzero"`
	StructuredOutputWithTools       bool              `json:",omitzero"`
	RetryPolicy                     RetryPolicy       `json:",omitzero"`
}

DefinitionDescriptor is the complete secret-free behavioral projection used by rig identity and durable audit records.

func (DefinitionDescriptor) Validate

func (d DefinitionDescriptor) Validate() error

Validate checks the complete descriptor-only constructor domain without requiring the raw system prompt or an inference client.

type DefinitionError

type DefinitionError struct {
	Kind  DefinitionErrorKind
	Field string
	Cause error
}

DefinitionError reports a definition boundary failure without retaining raw prompts, model endpoints, or client identity in its message.

func (*DefinitionError) Error

func (e *DefinitionError) Error() string

func (*DefinitionError) Unwrap

func (e *DefinitionError) Unwrap() error

type DefinitionErrorKind

type DefinitionErrorKind string

DefinitionErrorKind identifies an invalid immutable hustle definition.

const (
	DefinitionMissingName           DefinitionErrorKind = "missing_name"
	DefinitionReservedName          DefinitionErrorKind = "reserved_name"
	DefinitionNilOption             DefinitionErrorKind = "nil_option"
	DefinitionDuplicateOption       DefinitionErrorKind = "duplicate_option"
	DefinitionInvalidParticipation  DefinitionErrorKind = "invalid_participation"
	DefinitionInvalidModelSource    DefinitionErrorKind = "invalid_model_source"
	DefinitionMissingModelSource    DefinitionErrorKind = "missing_model_source"
	DefinitionInvalidClient         DefinitionErrorKind = "invalid_client"
	DefinitionInvalidModel          DefinitionErrorKind = "invalid_model"
	DefinitionInvalidTimeout        DefinitionErrorKind = "invalid_timeout"
	DefinitionInvalidLimits         DefinitionErrorKind = "invalid_limits"
	DefinitionInvalidSystemPrompt   DefinitionErrorKind = "invalid_system_prompt"
	DefinitionInvalidPromptRevision DefinitionErrorKind = "invalid_prompt_revision"
	DefinitionMissingPolicyRevision DefinitionErrorKind = "missing_policy_revision"
	DefinitionInvalidPolicyRevision DefinitionErrorKind = "invalid_policy_revision"
	DefinitionInvalidOutputSchema   DefinitionErrorKind = "invalid_output_schema"
	DefinitionInvalidEvidenceTools  DefinitionErrorKind = "invalid_evidence_tools"
	DefinitionInvalidRetryPolicy    DefinitionErrorKind = "invalid_retry_policy"
)

type EvidenceBindings

type EvidenceBindings struct {
	SessionID     uuid.UUID
	LoopID        uuid.UUID
	ReadWorkspace *tool.ReadWorkspaceBinding
}

EvidenceBindings supplies only the invocation origin and structurally read-only workspace capability needed to build one run's evidence catalog. It intentionally cannot carry mutation, delegation, gate, grant, session, observation, or loop-control capabilities.

type EvidenceToolPolicy

type EvidenceToolPolicy struct {
	Revision    string
	Limits      ToolLoopLimits
	Definitions []tool.Definition
}

EvidenceToolPolicy is the immutable-definition input for a bounded evidence loop. Definitions are copied when the option is created and by Clone.

func (EvidenceToolPolicy) Clone

Clone returns a policy with an independently owned definition slice.

type InferenceBinding

type InferenceBinding struct {
	Client inference.Client
	Model  model.Model
}

InferenceBinding pairs a client with its validated, secret-free model.

type Limits

type Limits struct {
	InputBytes  int
	OutputBytes int
}

Limits bounds the serialized request and response payloads.

type ModelResolver

type ModelResolver interface {
	ResolveHustleModel(context.Context, uuid.UUID) (InferenceBinding, error)
}

ModelResolver resolves the exact originating loop's live inference binding.

type ModelSource

type ModelSource uint8

ModelSource selects how an invocation obtains its inference binding.

const (
	ModelSourceUnknown ModelSource = iota
	ModelSourceCurrentLoop
	ModelSourceNamed
)

type Name

type Name string

Name is the stable registration name of one hustle definition.

func (Name) Validate

func (n Name) Validate() error

Validate applies the stable hustle-name contract while preserving the caller's exact spelling. Whitespace is used only to detect empty or reserved names; it is not canonicalized away.

type Option

type Option func(*definitionOptions) error

Option contributes one immutable definition property.

func WithCurrentLoopModel

func WithCurrentLoopModel() Option

WithCurrentLoopModel resolves the originating loop's live model on every run.

func WithEvidenceTools

func WithEvidenceTools(policy EvidenceToolPolicy) Option

WithEvidenceTools enables a bounded evidence-tool loop. The option owns a defensive copy immediately; the zero policy explicitly leaves tools off.

func WithLimits

func WithLimits(limits Limits) Option

WithLimits sets serialized input and output byte limits.

func WithName

func WithName(name Name) Option

WithName sets the stable definition name.

func WithNamedInference

func WithNamedInference(client inference.Client, model model.Model) Option

WithNamedInference freezes a named client/model pair in the definition.

func WithOutputSchema

func WithOutputSchema(output inference.OutputSchema) Option

WithOutputSchema freezes one optional provider-neutral structured-output policy. The option owns a clone immediately so caller mutations made before Define cannot alter the definition.

func WithParticipation

func WithParticipation(participation Participation) Option

WithParticipation selects the definition's fixed execution lane.

func WithPolicyRevision

func WithPolicyRevision(revision string) Option

WithPolicyRevision identifies opaque parser and request-policy behavior.

func WithRetryPolicy

func WithRetryPolicy(policy RetryPolicy) Option

WithRetryPolicy selects one immutable retry policy. Classified retry is intentionally limited to evidence-backed reviewer definitions.

func WithSystemPrompt

func WithSystemPrompt(prompt, revision string) Option

WithSystemPrompt freezes the raw prompt and its public revision label.

func WithTimeout

func WithTimeout(timeout time.Duration) Option

WithTimeout sets the exact invocation timeout.

type Outcome

type Outcome struct {
	Result *Result
	Err    error
}

Outcome carries exactly one terminal result or error.

type Participation

type Participation uint8

Participation selects the session-global execution lane.

const (
	ParticipationUnknown Participation = iota
	ParticipationBlocking
	ParticipationBackground
)

type ReasonCode

type ReasonCode uint8

ReasonCode is the bounded, security-safe classification of a hustle failure.

const (
	ReasonUnknown ReasonCode = iota
	ReasonRejected
	ReasonCanceled
	ReasonTimeout
	ReasonModelResolution
	ReasonInference
	ReasonInvalidOutput
	ReasonTerminal
	ReasonFinalization
	ReasonInternal
)

func (ReasonCode) Valid

func (r ReasonCode) Valid() bool

Valid reports whether the reason is recognized for durable audit.

type Request

type Request struct {
	Name  Name
	Cause identity.Cause
	Input json.RawMessage
	// SecurityCeiling is the per-invocation evidence-tool containment ceiling
	// THIS SPECIFIC run's evidence catalog must be bound against (design
	// §13.1, §21). Empty means no per-request override: a Hustle without an
	// evidence-tool concept (e.g. compaction) never sets it, and never reaches
	// the evidence-binding path that would consume it. A permission-review
	// Hustle always sets it from that review's own frozen basis
	// (gate.ReviewBasis.SecurityCeiling, captured once at StartPermissionReview
	// — see internal/sessionruntime/gates.go's respondFromClassifier doc
	// comment), never a session-wide constant, so a long session's later
	// review is bound against ITS OWN current ceiling rather than one frozen
	// at controller construction.
	SecurityCeiling string
}

Request is the shared runtime's data-only serialization envelope.

type ResolveError

type ResolveError struct {
	Kind  ResolveErrorKind
	Cause error
}

ResolveError reports a model-resolution failure without exposing model or client details. Cause remains available to trusted callers through errors.Is.

func (*ResolveError) Error

func (e *ResolveError) Error() string

func (*ResolveError) Unwrap

func (e *ResolveError) Unwrap() error

type ResolveErrorKind

type ResolveErrorKind string

ResolveErrorKind identifies an inference binding resolution failure.

const (
	ResolveInvalidContext ResolveErrorKind = "invalid_context"
	ResolveInvalidLoopID  ResolveErrorKind = "invalid_loop_id"
	ResolveModelFailed    ResolveErrorKind = "model_failed"
	ResolveInvalidBinding ResolveErrorKind = "invalid_binding"
)

type Result

type Result struct {
	Output json.RawMessage
	Usage  *content.Usage
}

Result is the validated serialized output and normalized usage.

type RetryPolicy

type RetryPolicy uint8

RetryPolicy selects the immutable, bounded retry behavior of one definition. The zero value preserves the historical single-attempt behavior.

const (
	RetryPolicyNone RetryPolicy = iota
	// RetryPolicyClassifiedOnce permits one clean restart after a closed set of
	// transient inference or recoverable terminal-parse failures.
	RetryPolicyClassifiedOnce
)

func (RetryPolicy) Valid

func (p RetryPolicy) Valid() bool

Valid reports whether the policy is a recognized immutable behavior.

type RevisionError

type RevisionError struct{ Cause error }

RevisionError reports the impossible failure to encode a closed, typed policy projection. It exists so even programmer failures retain type identity.

func (*RevisionError) Error

func (e *RevisionError) Error() string

func (*RevisionError) Unwrap

func (e *RevisionError) Unwrap() error

type RunID

type RunID uuid.UUID

RunID identifies one hustle invocation.

type Stage

type Stage uint8

Stage identifies the bounded execution phase in which a hustle failed.

const (
	StageUnknown Stage = iota
	StageQueue
	StageModelResolution
	StageInference
	StageOutput
	StageTerminal
	StageFinalization
)

func (Stage) Valid

func (s Stage) Valid() bool

Valid reports whether the stage is a durable, recognized failure phase.

type TerminalStatus

type TerminalStatus uint8

TerminalStatus is the bounded outcome dimension used by durable usage aggregates. Interrupted attempts are deliberately not terminal.

const (
	TerminalStatusUnknown TerminalStatus = iota
	TerminalStatusCompleted
	TerminalStatusFailed
)

func (TerminalStatus) Valid

func (s TerminalStatus) Valid() bool

Valid reports whether the value is a durable terminal outcome.

type ToolLoopLimits

type ToolLoopLimits struct {
	MaxRounds        int
	MaxCalls         int
	MaxCallsPerRound int
	MaxResultBytes   int
	MaxEvidenceBytes int
}

ToolLoopLimits bounds one opt-in evidence-tool conversation. Every field is required for an enabled policy; the zero value means evidence tools are off.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL