Skip to main content

When a package needs another package to run work, call a named Bitfield target. Do not import the implementation function directly.

A keyboard package changes the selected agent. A notifications package changes the notification mode. A search box asks a search target for results. In traditional code, these become direct service calls. In Bitfield, they are action requests.A correct caller uses the action name and payload shape. It does not import the implementation file for the work.

Traditional app shape

import { updateSelectedAgent } from '../selected-agent/service';

export async function onShortcut(agentId: string) {
  await updateSelectedAgent(agentId);
}
That direct call now depends on the selected-agent package implementation. If the implementation moves, changes runtime, or becomes a slot, the caller changes too.

Bitfield shape

import { sendRequestToBitfieldTarget } from '@bitfield/runtime-kit';

export async function onShortcut(agentId: string) {
  await sendRequestToBitfieldTarget({
    target: 'selected-agent.update',
    payload: { value: agentId },
  });
}
The action name is the public target. The implementation can change without making the caller import new code.

Code translation

Traditional codeBitfield code
Import a service function.Call sendRequestToBitfieldTarget(...).
Pass private objects.Send a small public payload.
Return whatever the function returns.Decode reply bytes according to the action reply shape.
Catch implementation errors.Handle request failure and bad replies.

Good action requests

await sendRequestToBitfieldTarget({
  target: 'notification-mode.update',
  payload: { modeId: 'quiet' },
});
const reply = await sendRequestToBitfieldTarget({
  target: 'project-preview.refresh-status',
  payload: { projectId: 'app' },
});
const reply = await sendRequestToBitfieldTarget({
  target: 'help.search',
  payload: { query: 'activation key' },
});
Each request names the job and sends the smallest public payload needed for that job.

Four request situations

Refresh a preview

Direct service call

import { refreshPreview } from '../project-preview/refresh';

await refreshPreview(projectId);

Use the action name

await sendRequestToBitfieldTarget({
  target: 'project-preview.refresh-status',
  payload: { projectId },
});
The caller uses the job name and payload. It does not import the implementation file.

Change selected agent

Direct store mutation

import { selectedAgentStore } from '../agents/store';

selectedAgentStore.set(agentId);

Ask for the change

await sendRequestToBitfieldTarget({
  target: 'selected-agent.update',
  payload: { agentId },
});
The same request can come from a keyboard shortcut, command palette, sidebar, or future shell.

Update notification mode

Direct package function

import { setNotificationMode } from '../notifications/mode';

await setNotificationMode('quiet');

Send the public request

await sendRequestToBitfieldTarget({
  target: 'notification-mode.update',
  payload: { mode: 'quiet' },
});
Settings and command surfaces can call the same public action without sharing notification package code.

Search help content

Private search import

import { searchHelpIndex } from '../help/search-index';

const results = searchHelpIndex(query);

Search through the target

const results = await sendRequestToBitfieldTarget({
  target: 'help.search',
  payload: { query },
});
The caller sends the question. The package that owns the searchable material decides how to answer.

Request versus state

You need to…UseReason
Show the currently selected file.Data name or Bitfield state read.Nothing has to run just to observe it.
Change which file is selected.Action request or state write through the package contract.A change may need validation, side effects, or fan-out.
Render preview URL.Data name.The preview package already prepared the view.
Refresh preview URL.Action request.Work has to run.
Toggle a dropdown.Private UI state.It is visual-only.
Change notification mode.Action request.Multiple callers can request the same product action.

Reply and error boundary

The caller may use the reply shape. It must not import the implementation path.
const reply = await sendRequestToBitfieldTarget({
  target: 'project-preview.refresh-status',
  payload: { projectId },
});

if (!reply.ok) {
  showError(reply.error.message);
}
If the target returns bytes, text, JSON, or a structured result, the page that defines the target should say that. The caller should decode only the documented reply shape.

Review checklist

CheckGood answer
Does the caller import implementation code?No. It sends an action request.
Is the payload small and public?Yes. It contains only the fields the action reply shape needs.
Is the caller guessing the reply shape?No. It decodes the documented reply shape.
Can the same request come from another shell?Yes. React is one adapter, not the action owner.

What this prevents

Bad shapeWhy it breaksBetter shape
Button imports target implementation.UI and work runtime become coupled.Button sends an action request.
Caller passes a huge private object.Action reply shape depends on caller private code.Caller sends a small public payload.
Caller assumes every reply is JSON.Non-JSON targets break the caller.Decode according to the action reply shape.
Caller updates Bitfield state and runs side effects itself.Work logic spreads across packages.Request the package target that owns that job.
React is one adapter that can send an action request. The same target should make sense from a keyboard shortcut, a terminal command, a Swift screen, a background job, or a future shell.

Common failures

SymptomLikely mistakeFix
Button works only in one package.It imported a local implementation.Move the call to an action request.
Reply parsing breaks.The caller guessed the reply shape.Document the action reply shape and decode only that.
Work runs twice.UI code both updates state and calls a worker.Pick the action request as the work boundary.
A new service file appears just to cross the boundary.It copied the traditional app shape.Call a named target.

Review check

If caller code imports a function from another package to “do work,” stop. The public API is an action request. The caller may use the action name, payload, reply shape, and error states. It must not import the implementation file.

Next

Last modified on May 10, 2026