stech / agent control planev0.18.1
stech/

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, and canManage. 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.ts

api/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.