Projects API
Projects are daemon-owned coordination resources layered over sessions, runs, and channels. They do not replace the normal execution model:- sessions still execute work
- runs still represent concrete execution
- channels still hold public conversation
kheish-daemon projects ....
Endpoint inventory
Projects:GET /v1/projectsPOST /v1/projectsGET /v1/projects/{project_id}PUT /v1/projects/{project_id}DELETE /v1/projects/{project_id}
GET /v1/projects/{project_id}/membersPOST /v1/projects/{project_id}/membersDELETE /v1/projects/{project_id}/members/{member_id}
GET /v1/projects/{project_id}/channelsPOST /v1/projects/{project_id}/channelsDELETE /v1/projects/{project_id}/channels/{channel_id}
GET /v1/projects/{project_id}/tasksPOST /v1/projects/{project_id}/tasksGET /v1/projects/{project_id}/tasks/{task_id}PUT /v1/projects/{project_id}/tasks/{task_id}DELETE /v1/projects/{project_id}/tasks/{task_id}POST /v1/projects/{project_id}/tasks/{task_id}/start
List projects
GET /v1/projects supports:
querystatusmember_session_idchannel_id
query matches project id, display name, and description.
Current project statuses are:
activepausedcompletedarchived
Create a project
POST /v1/projects accepts:
project_idoptional stable caller-selected identifierdisplay_namerequireddescriptionoptionalstatusoptional, defaulting toactivemembersoptional initial project memberschannel_linksoptional initial linked channelsmetadataoptional caller metadata
display_nameis required- channel ids must already exist
- one project cannot register the same session twice under different member ids
default_for_new_tasks, the daemon marks the first linked channel as the default.
Project members
POST /v1/projects/{project_id}/members upserts one member.
Fields:
member_idrequired stable identifier inside the projectmember_kindrequired:human_actororsessiondisplay_namerequiredsession_idoptional forsessionmembersagent_idoptional forsessionmembersactor_idoptional forhuman_actormembersroleoptionalexpertise_tagsoptionalmetadataoptional
session_idandagent_idare mutually exclusive- omitting both on a session member falls back to
member_id - task ownership is only valid for session-backed members
- one session cannot appear twice in the same project under different member ids
DELETE /v1/projects/{project_id}/members/{member_id} removes one member.
Removal is rejected while that member still owns any non-terminal project task.
Linked channels
POST /v1/projects/{project_id}/channels upserts one linked channel.
Fields:
channel_idrequired existing daemon channel idroleoptional semantic labeldefault_for_new_tasksoptional booleanmirror_membersoptional booleanmetadataoptional
- at most one linked channel is kept as
default_for_new_tasks=true - when
mirror_members=true, project members are mirrored into the linked channel - mirrored channel member ids are namespaced as
project:{project_id}:{member_id} - if the current default link is removed and another link remains, the first remaining link becomes the new default
DELETE /v1/projects/{project_id}/channels/{channel_id} removes one link.
Removal is rejected while non-terminal project tasks still reference that channel as their current discussion channel.
Project tasks
Project tasks currently store:project_task_idproject_idtitledescriptionstatusassignee_member_idprimary_session_idlatest_run_iddiscussionblocked_byoutputcreated_at_msupdated_at_msmetadata
pendingin_progressblockedcompletedfailedcancelled
List project tasks
GET /v1/projects/{project_id}/tasks supports:
querystatusassignee_member_id
query matches task id, title, description, and recorded output.
Create a project task
POST /v1/projects/{project_id}/tasks accepts:
project_task_idoptional stable caller-selected identifiertitlerequireddescriptionoptionalstatusoptional, defaulting topendingassignmentdiscussion_channel_iddiscussion_thread_root_message_idblocked_bylatest_run_idoutputmetadata
assignment accepts exactly one of:
assignee_member_idassignee_session_idassignee_agent_id
- the project must be
active titleis required- assignments resolve to an existing project member
- only session-backed members can be task assignees
discussion_channel_idanddiscussion_thread_root_message_idmust be provided together- explicit discussion channels must already be linked to the project
latest_run_id, when set, must belong to the assignee session- dependency ids must already exist in the same project
- self-dependency and dependency cycles are rejected
Update a project task
PUT /v1/projects/{project_id}/tasks/{task_id} accepts partial updates for:
titledescriptionstatusassignmentclear_assignmentdiscussion_channel_iddiscussion_thread_root_message_idclear_discussionblocked_bylatest_run_idoutputclear_outputmetadata
- assignment still accepts exactly one of member/session/agent id
latest_run_idis validated against the effective assignee session- replacing
blocked_byre-runs dependency validation and cycle checks
Delete a project task
DELETE /v1/projects/{project_id}/tasks/{task_id} removes one task.
Deletion is rejected while other project tasks still depend on it through blocked_by.
Start a project task
POST /v1/projects/{project_id}/tasks/{task_id}/start creates a normal daemon run in the assigned session.
Request fields:
provideroptional route overridemodeloptional model overridekickoff_messageoptional replacement kickoff text when the daemon creates a new discussion threadmetadataoptional caller metadata attached to the spawned run
provider can carry a configured route_id rather than only a raw provider family.
Current start rules:
- the project must be
active - the task must be assigned
- the assignee must be a session-backed project member
- every dependency must already be
completed - terminal tasks must be reopened before start
- a second start is rejected while
latest_run_idstill points at a non-terminal run
- if the task already has a valid linked discussion thread, the daemon reuses it
- otherwise, if the project has one linked default channel, the daemon can create a kickoff thread there
- otherwise, the run still starts without a public discussion thread
- the daemon submits a normal input run in the assignee session
- the task stores the new
latest_run_id
- the created
RunView
- queued run -> task stays
pending - every other current run state -> task becomes
in_progress
Operational notes
DELETE /v1/projects/{project_id}is rejected while the project still has tasks- deleting a channel is rejected while any project still links it
- ending a session is rejected while it is still a project member
- ending a session is also rejected while it still owns non-terminal project tasks
