Company agent directory
Automatic requests use the existing Anthropic provider and organization key resolver
(organization key, otherwise the configured platform key). Existing
organization caps and token accounting
and guardrail policies apply. The model is
claude-sonnet-4-6.
The server owns the tool list; no original native session or organization-wide MCP
tools are loaded. Missing model credentials, a cap refusal or a policy block
produce a failed request. These requests do not create hosted run history,
guardrail audit rows or run webhooks.
The Network Doctor company knowledge profile requires both
STECH_COMPANY_NETWORK_DOCTOR_ORGANIZATION_ID and
STECH_COMPANY_NETWORK_DOCTOR_WORKSPACE_ID in the API server environment. Both
must match the request's organization and workspace; an agent's name does not
select this profile. Leave both unset to use only generic profiles. Setting just
one fails API startup. Its three fixed diagnostic tools report availability
through the configured read-only connection.
Knowledge answers and diagnosis of information supplied by the requester still
run automatically.
The /agents page lists published native workspaces alongside existing hosted
deployments. A workspace owner can edit its directory name and description and
turn company visibility on or off in Company sharing on /workspaces/[id].
Workspaces are unlisted until the owner selects List this agent for company
members and saves. Clear that option and save to unlist an existing entry.
View agent opens its metadata and private question form at /agents/company/[id].
Access #
- Active members of the workspace's organization can see enabled publications.
- The active owner can also see their own disabled publications.
- Only the workspace owner can publish or edit it. An organization admin does not gain access to another member's workspace through this feature.
- Removing the owner or viewer from the organization removes directory access.
- Directory responses contain only
id,name,description,engine,availability, andcanManage. Workspace IDs, native sessions, task history, filesystem paths and credentials are not returned.
Existing /workspaces endpoints remain owner-only. Names and descriptions are
owner-published company content; do not put private instructions or secrets in
them.
API #
All routes are under /v1/orgs/:slug/company-agents and use the existing session
or organization API-key authentication. Both require an identified user with
active membership; an API key is restricted to its own organization.
| Method and suffix | Result |
|---|---|
GET / |
{ agents: [...] }, visible publications |
POST /select |
{ prompt, agentId? } → a selected agent, needs_choice, or unavailable; no task is started |
GET /:id |
{ agent }, one visible publication |
GET /workspaces/:workspaceId |
Owner only; { agent } or { agent: null } |
PUT /workspaces/:workspaceId |
Owner only; save { name, description, enabled } |
Names must contain text and fit 128 characters; descriptions fit 2,000. Publication is an upsert by workspace, so editing it keeps its directory ID. Deleting a workspace deletes its publication and saved requests, including replies.
Choosing an agent #
POST /select accepts only { prompt, agentId? }: a nonblank prompt of at most
8,000 characters and an optional published agentId of 1–128 characters. Neither
field may contain a NUL character. Invalid JSON, fields or values return
400 invalid_company_agent_selection; bodies over 64 KiB return
413 request_too_large. An explicit ID wins and must be enabled and visible to the
caller; otherwise it returns the same 404 as an unavailable agent. With no explicit
ID, zero candidates returns { "status": "unavailable" }; one returns its ID
without a model call. Native workspace liveness does not restrict server answers.
For multiple candidates, the API can use Jev Choice
with a private server TYPESAFE_API_KEY. It sends only the prompt, published names
and descriptions, and server-defined capability summaries. Organization input
policies check the aggregate input length. When pii.redact is enabled, each raw
field is redacted before transmission; see the
guardrail catalog. Real agent IDs,
owner sessions, credentials and knowledge records are excluded. The pinned model
is jev-1.13.0; only the returned local shortlist label can become an agent ID.
The server checks current caller access and unchanged candidates before and after
classification. No database lock is held during the provider call.
Success returns { "status": "selected", "agentId": "...", "method": "explicit" | "single" | "jev" }.
Missing configuration, more than 32 candidates, ambiguity, malformed output or
provider failure returns { "status": "needs_choice" }. A confidence floor of
0.8 is a conservative routing heuristic, not a measured accuracy guarantee or
authorization. Each provider request has a ten-second deadline, a 64 KiB response
limit, no redirects and no retry. The existing usage cap applies; paid
classification has a process-local burst limit of ten attempts per organization/member per minute
(429 with Retry-After). Explicit and single-agent choices do not consume it.
An enforced cost policy without Jev pricing blocks classification rather than
assuming it is free.
Cap refusal returns 402 usage_cap_exceeded; input or model policy refusal
returns 403 company_agent_selection_blocked. These are errors, not
needs_choice. Caller authorization lost during classification returns 401 or
403; changed candidates return needs_choice.
Each provider attempt records an org-scoped agent_selection usage event before
dispatch; it is not an agent run, measured token count or billable price. Pricing,
quotas and BYOK selection configuration are separate work. Existing input/output
token aggregates do not include Jev tokens. Failed usage persistence prevents
dispatch. Prompt text is not written to usage records.
Submit a selected ID through the existing POST /:id/requests. That operation
rechecks current membership, publication and key access; a selection response is
never permission to execute. The current app and Slack still use explicit agents;
this API is the selection building block, not multi-agent channel orchestration.
Private questions and automatic answers #
A member can ask a private question from a published agent page. My requests
(/agents/requests) lists that member's private questions and linked channel
requests. Each request has a separate page at /agents/requests/[id], with its
question, automatic answer and any proposed change. Inbox (/agents/inbox)
lists questions addressed to the signed-in workspace owner, including proposals
that need their decision.
Request links can carry ?company=<company ID or slug> to load the originating
company even when another company is selected. Sign-in and refresh preserve that
reference without changing the selected company; current membership and original
participant checks still apply.
Submitting saves a queued request. The server worker produces an automatic answer;
the UI distinguishes queued, running, answered and failed. The open request
checks its saved status every three seconds, with sequential reads bounded to 60
reads or three minutes. Channel requests keep checking after the answer finishes
while delivery is pending or sending, within the same limit. Updates stop when
answer processing and any channel delivery finish, or on a read error, navigation
or the limit. Refresh reads the saved answer and delivery status. Polling never
resubmits a question, and failed execution is not retried automatically.
For the knowledge available to generic agents and the bound Network Doctor profile, see Reviewed knowledge for company agents. Each request contains one question and answer. Answers and diagnostic guidance do not wait for manual owner replies; there is no manual reply endpoint.
A failed send keeps the question draft on the current page; drafts do not survive navigation or reload. If the save cannot be confirmed, My requests (new tab) lets the requester check before trying again. A manual resend may create a second question. The UI prevents duplicate submission while a save is in flight.
Only the active requester and the currently authorized recipient owner can read a request in Stech. Other members and organization administrators do not gain web access, including through channel membership. Channel requests are labeled separately; their Slack audience and delivery rules also apply. The recipient owner is fixed when the question is submitted: a replacement workspace owner cannot inherit earlier private questions. Active membership, current publication and original ownership are checked before and during automatic processing and before saving the answer. Owner decisions recheck membership and ownership.
Unlisting an agent blocks new questions and queued execution, and prevents an in-flight answer from being saved. Existing questions, saved answers and proposal decisions remain available to their authorized participants. Requests are separate from native workspace tasks, sessions and events. An offline owner workspace does not prevent a listed agent from receiving a question for an automatic answer.
Proposed changes #
An answer may include a proposal containing target, change, impact,
verification and rollback. The requester can read the proposal and its decision.
Only the authorized owner sees Approve for owner execution and Reject change
when canReviewChange is true and the approval is pending.
Approval records the owner's decision only. Approved for owner execution does
not mean the change was applied: this service does not execute infrastructure
changes. A proposal can move from pending to approved or rejected once. A
conflicting decision asks the owner to refresh the saved result.
The following routes share the /v1/orgs/:slug/company-agents prefix:
| Method and suffix | Result |
|---|---|
GET /requests |
{ requests: [...] }, caller's submitted questions |
GET /inbox |
{ requests: [...] }, current owner's assigned questions |
GET /requests/:requestId |
{ request }, authorized participant only |
POST /:id/requests |
Submit { prompt } to a listed agent; returns queued request |
POST /requests/:requestId/approval |
Owner records { decision: "approved" | "rejected" } |
The request metadata type defines response fields, including audience and separate channel-delivery status alongside the question, answer and proposal decision. Question text is bounded to 1–8,000 characters of plain text. Questions, answers and proposal fields render as plain text. Attachments and follow-up conversation history are deferred. These routes do not dispatch native tasks.
Workspace availability #
Availability describes the underlying owner workspace: disabled, paused,
offline, unprotected, busy, or ready. After host admission and live-worker
checks pass, running work is busy, even while its changes await backup. An
idle workspace is ready only when the existing protection and handoff checks
also pass; otherwise it is unprotected. Displayed state is a point-in-time
observation, refreshed by Refresh agents or Refresh details. It is separate
from automatic request execution status.
Directory publication and private questions do not grant members native task submission, private owner history, infrastructure change tools or access to the owner's existing conversation. Existing hosted-agent chat and runs remain available through their existing routes.
Checks #
api/tests/company-agents.integration.test.ts and
api/tests/company-agent-requests.integration.test.ts use real authentication,
production route composition and migrations in isolated databases. The request
suite covers participant access, unlisting, ownership and membership changes,
API-key scope, automatic request state and owner decisions without native task dispatch.
api/tests/company-agent-request-worker.integration.test.ts covers queue claims,
concurrent workers, authorization changes during processing, stale attempts and
safe failure codes against the same migration chain.
Set TEST_DATABASE_URL to a disposable local PostgreSQL server's /postgres
database; pgvector must be installed. Each suite creates and drops its own
database. Do not use a shared or production database. Scope TEST_DATABASE_URL
to these suites, not unrelated integration suites:
bun test ./api/tests/company-agents.integration.test.ts \
./api/tests/company-agent-requests.integration.test.ts \
./api/tests/company-agent-request-worker.integration.test.tsapi/tests/company-agent-answer.test.ts exercises the real runtime loop with a
mocked provider: fresh history, profile binding, fixed diagnostic tools, proposals,
policy refusal and usage accounting. It does not call a live model or diagnostic
transport.
The app's company-agent component tests cover real links, publication controls,
and unavailable versus empty states. Tests under app/app/agents/requests/ also
cover escaped plain-text rendering, owner change controls, bounded status
updates, request-client error handling and server-page access routing. These are
static component, client and server-page checks; they do not exercise browser
interactions or live Network Doctor execution. Pilot acceptance still requires
a deployed, authenticated automatic response; these tests do not establish it.
Messaging workspaces #
See Company messaging connections for installation, channel assignment, member linking and automatic Slack replies.